Compare commits

...

30 Commits

Author SHA1 Message Date
Nigel Stewart
70fb4e4814
Extend cxxopts API to support as_optional in addition to as query of values (#421)
Co-authored-by: Nigel Stewart <nigel.stewart@emesent.io>
2024-02-29 20:27:19 +11:00
Jarryd Beck
4bf61f0869 Add changes for 3.2.1 2024-02-20 19:39:52 +11:00
jarro2783
1e175bc1db
Fix ordering of parse_value functions (#420)
Fixes #419. Put the definitions of parse_value functions before they
are called so that the right ones are chosen.
2024-02-20 19:38:44 +11:00
jarro2783
3bf268481d
Make cxxopts.hpp ready for 3.2
Update version number in header, ready for 3.2
2024-02-15 16:24:38 +11:00
jarro2783
f7194e9c59
Update CHANGELOG.md
Make changelog ready for 3.2
2024-02-15 16:23:58 +11:00
jarro2783
71d5bf7265
Update CHANGELOG.md 2024-02-15 16:23:15 +11:00
jarro2783
d3de64f26e
Update CHANGELOG.md 2024-02-15 16:21:30 +11:00
ololuki
e84ab5f67c
Fix overflow error in integer_parser. (#417)
Fixes #290.
Checking for overflow should be done before integer overflows.
There are two checks:
(result > limit / base) is used for limits greater than rounded up to base,
e.g. for 65535 it will activate for 65540 and higher.
(result * base > limit - digit) is used for limit+1 to limit+n below
next base rounded number, e.g. 65536 up to 65539.
2024-02-01 16:51:32 +11:00
Jarryd Beck
7bf29108d5 Update catch2 to latest 2.x 2024-01-15 21:04:41 +11:00
Nigel Stewart
cd61c685eb
Ordered vector of group names to preserve order of groups (#416)
Co-authored-by: Nigel Stewart <nigel.stewart@emesent.io>
2024-01-15 20:52:54 +11:00
Blake-Madden
554396be3b
Minor typo in README.md (#411)
Fix grammar in README
2023-11-25 11:49:16 +11:00
Jarryd Beck
78b90d8f0c Remove incorrect sentence in README
Fixes #410. Removes sentence about ParseResult scope that is no longer correct.
2023-11-13 16:49:18 +11:00
Jarryd Beck
c8c932f891 Add CHANGELOG for #398 2023-06-15 07:35:14 +10:00
Alexander Galanin
ddc695ebac
Don't split by list delimiter in positional lists (#398) 2023-06-15 07:34:26 +10:00
Jarryd Beck
bf1b5a96e0 Update changelog 2023-06-15 07:18:53 +10:00
Jarryd Beck
90b318105f Fix unannoted fallthrough
Fixes #402.
2023-06-15 07:18:30 +10:00
Maximilian Knespel
a526762eb8
[fix] Avoid std::regex initialization during dlopen (#406) 2023-05-31 07:26:34 +10:00
Jarryd Beck
beda973ec6 Remove incorrect todo.
Fixes #404.
2023-05-22 17:31:17 +10:00
Julien Voisin
714a105fe6
Bump rules_fuzzing to the latest available version (#403) 2023-05-01 06:45:55 +10:00
Nigel Stewart
16cc254919
Output same exception messages for both Linux and Windows (#395)
Co-authored-by: Nigel Stewart <nigel.stewart@emesent.io>
2023-04-03 17:15:01 +10:00
Jarryd Beck
120205ac5a Fix cast warning in Unicode function
Fixes #339. Fixes a sign conversion warning in a function compiled by
-DCXXOPTS_USE_UNICODE_HELP=1.
2023-03-15 07:09:09 +11:00
Jarryd Beck
89a9d334f2 Add note about exceptions in Changelog 2023-03-13 07:52:48 +11:00
Nathaniel Brough
44739d3023
feat(fuzz): Adds trigger for fuzzing in the CI (#392) 2023-02-22 07:03:38 +11:00
Demian Hespe
134f60f973
Fix noexcept warning (#391) 2023-02-20 07:25:13 +11:00
Jarryd Beck
eb787304d6 Fix version number 2023-02-16 07:02:16 +11:00
Jarryd Beck
c12ce65503 Update CHANGELOG for 3.1 release 2023-02-13 07:31:30 +11:00
Jarryd Beck
b80bdfddf0 Added missing CHANGELOG entry 2023-02-13 07:31:30 +11:00
Martijn Courteaux
799ee0e3e8
Add <locale> to be spec-compliant for std::isalnum (didn't work on MSVC). (#385) 2023-02-11 15:47:48 +11:00
Nathaniel Brough
58daccc945
feat(fuzzer): Adds fuzz tests (#386) 2023-02-06 07:30:51 +11:00
Jarryd Beck
e1f8c16702 Fix diagnostics pop error 2023-02-02 07:25:12 +11:00
11 changed files with 16714 additions and 8831 deletions

24
.github/workflows/cifuzz.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: CIFuzz
on: [pull_request]
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'cxxopts'
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'cxxopts'
language: c++
fuzz-seconds: 300
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts

View File

@ -20,11 +20,9 @@ jobs:
build-ubuntu: build-ubuntu:
strategy: strategy:
matrix: matrix:
os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ] os: [ ubuntu-20.04, ubuntu-22.04 ]
compiler: [ g++-9, g++-10, clang++ ] compiler: [ g++-9, g++-10, clang++ ]
include:
- os: ubuntu-18.04
compiler: g++-7
name: Build and Test on Ubuntu name: Build and Test on Ubuntu
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
steps: steps:

View File

@ -6,3 +6,11 @@ cc_library(
strip_include_prefix = "include", strip_include_prefix = "include",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
cc_fuzz_test(
name = "cxxopts_fuzz_test",
srcs = ["test/fuzz.cpp"],
deps = [":cxxopts"],
)

View File

@ -3,17 +3,51 @@
This is the changelog for `cxxopts`, a C++11 library for parsing command line This is the changelog for `cxxopts`, a C++11 library for parsing command line
options. The project adheres to semantic versioning. options. The project adheres to semantic versioning.
## Unreleased ## 3.2.1
### Bug fixes
* Fix compilation with optional on C++20.
## 3.2
### Bug fixes
* Fix unannotated fallthrough.
* Fix sign conversion with Unicode output.
* Don't initialize regex in static initialiser.
* Fix incorrect integer overflow checks.
### Added
* Add fuzzing to CI
### Changed
* Change quote output to '' to match Windows.
* Don't split positional arguments by the list delimiter.
* Order help groups by the order they were added.
## 3.1.1
### Bug Fixes
* Fixed version number in header.
* Fixed cast warning in Unicode function.
## 3.1
### Added ### Added
* Support for multiple long names for the same option (= multiple long aliases) * Support for multiple long names for the same option (= multiple long aliases)
* Add a `program()` function to retrieve the program name. * Add a `program()` function to retrieve the program name.
* Added a .clang-format file. * Added a .clang-format file.
* Added iterator and printing for a ParseResult.
### Changed ### Changed
* Cleanup exception code, add cxxopts::exceptions namespace. * Cleanup exception code, add cxxopts::exceptions namespace.
* Renamed several exceptions to be more descriptive, and added to a nested namespace.
### Bug Fixes ### Bug Fixes

View File

@ -85,9 +85,6 @@ result["opt"].as<type>()
to get its value. If "opt" doesn't exist, or isn't of the right type, then an to get its value. If "opt" doesn't exist, or isn't of the right type, then an
exception will be thrown. exception will be thrown.
Note that the result of `options.parse` should only be used as long as the
`options` object that created it is in scope.
## Unrecognised arguments ## Unrecognised arguments
You can allow unrecognised arguments to be skipped. This applies to both You can allow unrecognised arguments to be skipped. This applies to both
@ -196,8 +193,8 @@ therefore, `-o false` does not work.
## `std::vector<T>` values ## `std::vector<T>` values
Parsing of list of values in form of an `std::vector<T>` is also supported, as long as `T` Parsing a list of values into a `std::vector<T>` is also supported, as long as `T`
can be parsed. To separate single values in a list the definition `CXXOPTS_VECTOR_DELIMITER` can be parsed. To separate single values in a list the define symbol `CXXOPTS_VECTOR_DELIMITER`
is used, which is ',' by default. Ensure that you use no whitespaces between values because is used, which is ',' by default. Ensure that you use no whitespaces between values because
those would be interpreted as the next command line option. Example for a command line option those would be interpreted as the next command line option. Example for a command line option
that can be parsed as a `std::vector<double>`: that can be parsed as a `std::vector<double>`:
@ -278,7 +275,3 @@ GCC >= 4.9 or clang >= 3.1 with libc++ are known to work.
The following compilers are known not to work: The following compilers are known not to work:
* MSVC 2013 * MSVC 2013
# TODO list
* Allow unrecognised options.

View File

@ -0,0 +1,16 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_fuzzing",
sha256 = "d9002dd3cd6437017f08593124fdd1b13b3473c7b929ceb0e60d317cb9346118",
strip_prefix = "rules_fuzzing-0.3.2",
urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.zip"],
)
load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
rules_fuzzing_dependencies()
load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
rules_fuzzing_init()

View File

@ -27,6 +27,7 @@ THE SOFTWARE.
#ifndef CXXOPTS_HPP_INCLUDED #ifndef CXXOPTS_HPP_INCLUDED
#define CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED
#include <cstdlib>
#include <cstring> #include <cstring>
#include <exception> #include <exception>
#include <limits> #include <limits>
@ -40,6 +41,7 @@ THE SOFTWARE.
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <locale>
#ifdef CXXOPTS_NO_EXCEPTIONS #ifdef CXXOPTS_NO_EXCEPTIONS
#include <iostream> #include <iostream>
@ -72,6 +74,14 @@ THE SOFTWARE.
# endif # endif
#endif #endif
#define CXXOPTS_FALLTHROUGH
#ifdef __has_cpp_attribute
#if __has_cpp_attribute(fallthrough)
#undef CXXOPTS_FALLTHROUGH
#define CXXOPTS_FALLTHROUGH [[fallthrough]]
#endif
#endif
#if __cplusplus >= 201603L #if __cplusplus >= 201603L
#define CXXOPTS_NODISCARD [[nodiscard]] #define CXXOPTS_NODISCARD [[nodiscard]]
#else #else
@ -83,8 +93,8 @@ THE SOFTWARE.
#endif #endif
#define CXXOPTS__VERSION_MAJOR 3 #define CXXOPTS__VERSION_MAJOR 3
#define CXXOPTS__VERSION_MINOR 0 #define CXXOPTS__VERSION_MINOR 2
#define CXXOPTS__VERSION_PATCH 0 #define CXXOPTS__VERSION_PATCH 1
#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 #if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
#define CXXOPTS_NULL_DEREF_IGNORE #define CXXOPTS_NULL_DEREF_IGNORE
@ -229,10 +239,10 @@ stringAppend(String& s, Iterator begin, Iterator end)
} }
inline inline
std::size_t size_t
stringLength(const String& s) stringLength(const String& s)
{ {
return s.length(); return static_cast<size_t>(s.length());
} }
inline inline
@ -336,13 +346,8 @@ empty(const std::string& s)
namespace cxxopts { namespace cxxopts {
namespace { namespace {
#ifdef _WIN32
CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
#else
CXXOPTS_LINKONCE_CONST std::string LQUOTE("");
CXXOPTS_LINKONCE_CONST std::string RQUOTE("");
#endif
} // namespace } // namespace
// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
@ -364,6 +369,9 @@ class Value : public std::enable_shared_from_this<Value>
std::shared_ptr<Value> std::shared_ptr<Value>
clone() const = 0; clone() const = 0;
virtual void
add(const std::string& text) const = 0;
virtual void virtual void
parse(const std::string& text) const = 0; parse(const std::string& text) const = 0;
@ -757,29 +765,31 @@ inline ArguDesc ParseArgument(const char *arg, bool &matched)
namespace { namespace {
CXXOPTS_LINKONCE CXXOPTS_LINKONCE
std::basic_regex<char> integer_pattern const char* const integer_pattern =
("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)";
CXXOPTS_LINKONCE CXXOPTS_LINKONCE
std::basic_regex<char> truthy_pattern const char* const truthy_pattern =
("(t|T)(rue)?|1"); "(t|T)(rue)?|1";
CXXOPTS_LINKONCE CXXOPTS_LINKONCE
std::basic_regex<char> falsy_pattern const char* const falsy_pattern =
("(f|F)(alse)?|0"); "(f|F)(alse)?|0";
CXXOPTS_LINKONCE CXXOPTS_LINKONCE
std::basic_regex<char> option_matcher const char* const option_pattern =
("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"); "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)";
CXXOPTS_LINKONCE CXXOPTS_LINKONCE
std::basic_regex<char> option_specifier const char* const option_specifier_pattern =
("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"); "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*";
CXXOPTS_LINKONCE CXXOPTS_LINKONCE
std::basic_regex<char> option_specifier_separator(", *"); const char* const option_specifier_separator_pattern = ", *";
} // namespace } // namespace
inline IntegerDesc SplitInteger(const std::string &text) inline IntegerDesc SplitInteger(const std::string &text)
{ {
static const std::basic_regex<char> integer_matcher(integer_pattern);
std::smatch match; std::smatch match;
std::regex_match(text, match, integer_pattern); std::regex_match(text, match, integer_matcher);
if (match.length() == 0) if (match.length() == 0)
{ {
@ -803,15 +813,17 @@ inline IntegerDesc SplitInteger(const std::string &text)
inline bool IsTrueText(const std::string &text) inline bool IsTrueText(const std::string &text)
{ {
static const std::basic_regex<char> truthy_matcher(truthy_pattern);
std::smatch result; std::smatch result;
std::regex_match(text, result, truthy_pattern); std::regex_match(text, result, truthy_matcher);
return !result.empty(); return !result.empty();
} }
inline bool IsFalseText(const std::string &text) inline bool IsFalseText(const std::string &text)
{ {
static const std::basic_regex<char> falsy_matcher(falsy_pattern);
std::smatch result; std::smatch result;
std::regex_match(text, result, falsy_pattern); std::regex_match(text, result, falsy_matcher);
return !result.empty(); return !result.empty();
} }
@ -820,22 +832,25 @@ inline bool IsFalseText(const std::string &text)
// (without considering which or how many are single-character) // (without considering which or how many are single-character)
inline OptionNames split_option_names(const std::string &text) inline OptionNames split_option_names(const std::string &text)
{ {
if (!std::regex_match(text.c_str(), option_specifier)) static const std::basic_regex<char> option_specifier_matcher(option_specifier_pattern);
if (!std::regex_match(text.c_str(), option_specifier_matcher))
{ {
throw_or_mimic<exceptions::invalid_option_format>(text); throw_or_mimic<exceptions::invalid_option_format>(text);
} }
OptionNames split_names; OptionNames split_names;
static const std::basic_regex<char> option_specifier_separator_matcher(option_specifier_separator_pattern);
constexpr int use_non_matches { -1 }; constexpr int use_non_matches { -1 };
auto token_iterator = std::sregex_token_iterator( auto token_iterator = std::sregex_token_iterator(
text.begin(), text.end(), option_specifier_separator, use_non_matches); text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches);
std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
return split_names; return split_names;
} }
inline ArguDesc ParseArgument(const char *arg, bool &matched) inline ArguDesc ParseArgument(const char *arg, bool &matched)
{ {
static const std::basic_regex<char> option_matcher(option_pattern);
std::match_results<const char*> result; std::match_results<const char*> result;
std::regex_match(arg, result, option_matcher); std::regex_match(arg, result, option_matcher);
matched = !result.empty(); matched = !result.empty();
@ -958,13 +973,26 @@ integer_parser(const std::string& text, T& value)
throw_or_mimic<exceptions::incorrect_argument_type>(text); throw_or_mimic<exceptions::incorrect_argument_type>(text);
} }
const US next = static_cast<US>(result * base + digit); US limit = 0;
if (result > next) if (negative)
{
limit = static_cast<US>(std::abs(static_cast<intmax_t>(std::numeric_limits<T>::min())));
}
else
{
limit = std::numeric_limits<T>::max();
}
if (base != 0 && result > limit / base)
{
throw_or_mimic<exceptions::incorrect_argument_type>(text);
}
if (result * base > limit - digit)
{ {
throw_or_mimic<exceptions::incorrect_argument_type>(text); throw_or_mimic<exceptions::incorrect_argument_type>(text);
} }
result = next; result = static_cast<US>(result * base + digit);
} }
detail::check_signed_range<T>(negative, result, text); detail::check_signed_range<T>(negative, result, text);
@ -1034,25 +1062,6 @@ parse_value(const std::string& text, T& value) {
stringstream_parser(text, value); stringstream_parser(text, value);
} }
template <typename T>
void
parse_value(const std::string& text, std::vector<T>& value)
{
if (text.empty()) {
T v;
parse_value(text, v);
value.emplace_back(std::move(v));
return;
}
std::stringstream in(text);
std::string token;
while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
T v;
parse_value(token, v);
value.emplace_back(std::move(v));
}
}
#ifdef CXXOPTS_HAS_OPTIONAL #ifdef CXXOPTS_HAS_OPTIONAL
template <typename T> template <typename T>
void void
@ -1075,6 +1084,41 @@ void parse_value(const std::string& text, char& c)
c = text[0]; c = text[0];
} }
template <typename T>
void
parse_value(const std::string& text, std::vector<T>& value)
{
if (text.empty()) {
T v;
parse_value(text, v);
value.emplace_back(std::move(v));
return;
}
std::stringstream in(text);
std::string token;
while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
T v;
parse_value(token, v);
value.emplace_back(std::move(v));
}
}
template <typename T>
void
add_value(const std::string& text, T& value)
{
parse_value(text, value);
}
template <typename T>
void
add_value(const std::string& text, std::vector<T>& value)
{
T v;
add_value(text, v);
value.emplace_back(std::move(v));
}
template <typename T> template <typename T>
struct type_is_container struct type_is_container
{ {
@ -1126,6 +1170,12 @@ class abstract_value : public Value
m_implicit_value = rhs.m_implicit_value; m_implicit_value = rhs.m_implicit_value;
} }
void
add(const std::string& text) const override
{
add_value(text, *m_store);
}
void void
parse(const std::string& text) const override parse(const std::string& text) const override
{ {
@ -1411,6 +1461,19 @@ struct HelpGroupDetails
class OptionValue class OptionValue
{ {
public: public:
void
add
(
const std::shared_ptr<const OptionDetails>& details,
const std::string& text
)
{
ensure_value(details);
++m_count;
m_value->add(text);
m_long_names = &details->long_names();
}
void void
parse parse
( (
@ -1475,6 +1538,15 @@ CXXOPTS_DIAGNOSTIC_POP
return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get(); return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get();
} }
#ifdef CXXOPTS_HAS_OPTIONAL
template <typename T>
std::optional<T>
as_optional() const
{
return as<T>();
}
#endif
private: private:
void void
ensure_value(const std::shared_ptr<const OptionDetails>& details) ensure_value(const std::shared_ptr<const OptionDetails>& details)
@ -1497,7 +1569,7 @@ CXXOPTS_DIAGNOSTIC_POP
class KeyValue class KeyValue
{ {
public: public:
KeyValue(std::string key_, std::string value_) KeyValue(std::string key_, std::string value_) noexcept
: m_key(std::move(key_)) : m_key(std::move(key_))
, m_value(std::move(value_)) , m_value(std::move(value_))
{ {
@ -1551,6 +1623,7 @@ class ParseResult
// GCC complains about m_iter not being initialised in the member // GCC complains about m_iter not being initialised in the member
// initializer list // initializer list
CXXOPTS_DIAGNOSTIC_PUSH
CXXOPTS_IGNORE_WARNING("-Weffc++") CXXOPTS_IGNORE_WARNING("-Weffc++")
Iterator(const ParseResult *pr, bool end=false) Iterator(const ParseResult *pr, bool end=false)
: m_pr(pr) : m_pr(pr)
@ -1686,6 +1759,24 @@ CXXOPTS_DIAGNOSTIC_POP
return viter->second; return viter->second;
} }
#ifdef CXXOPTS_HAS_OPTIONAL
template <typename T>
std::optional<T>
as_optional(const std::string& option) const
{
auto iter = m_keys.find(option);
if (iter != m_keys.end())
{
auto viter = m_values.find(iter->second);
if (viter != m_values.end())
{
return viter->second.as_optional<T>();
}
}
return std::nullopt;
}
#endif
const std::vector<KeyValue>& const std::vector<KeyValue>&
arguments() const arguments() const
{ {
@ -1780,7 +1871,7 @@ class OptionParser
); );
void void
add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg);
void void
parse_option parse_option
@ -1983,6 +2074,7 @@ class Options
std::unordered_set<std::string> m_positional_set{}; std::unordered_set<std::string> m_positional_set{};
//mapping from groups to help options //mapping from groups to help options
std::vector<std::string> m_group{};
std::map<std::string, HelpGroupDetails> m_help{}; std::map<std::string, HelpGroupDetails> m_help{};
}; };
@ -2235,6 +2327,7 @@ OptionAdder::operator()
case 1: case 1:
short_name = *first_short_name_iter; short_name = *first_short_name_iter;
option_names.erase(first_short_name_iter); option_names.erase(first_short_name_iter);
CXXOPTS_FALLTHROUGH;
case 0: case 0:
break; break;
default: default:
@ -2326,9 +2419,13 @@ OptionParser::checked_parse_arg
inline inline
void void
OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg) OptionParser::add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg)
{ {
parse_option(iter->second, option, arg); auto hash = value->hash();
auto& result = m_parsed[hash];
result.add(value, arg);
m_sequential.emplace_back(value->essential_name(), arg);
} }
inline inline
@ -2345,14 +2442,14 @@ OptionParser::consume_positional(const std::string& a, PositionalListIterator& n
auto& result = m_parsed[iter->second->hash()]; auto& result = m_parsed[iter->second->hash()];
if (result.count() == 0) if (result.count() == 0)
{ {
add_to_option(iter, *next, a); add_to_option(iter->second, a);
++next; ++next;
return true; return true;
} }
++next; ++next;
continue; continue;
} }
add_to_option(iter, *next, a); add_to_option(iter->second, a);
return true; return true;
} }
throw_or_mimic<exceptions::no_such_option>(*next); throw_or_mimic<exceptions::no_such_option>(*next);
@ -2616,6 +2713,12 @@ Options::add_option
} }
//add the help details //add the help details
if (m_help.find(group) == m_help.end())
{
m_group.push_back(group);
}
auto& options = m_help[group]; auto& options = m_help[group];
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
@ -2747,19 +2850,7 @@ inline
void void
Options::generate_all_groups_help(String& result) const Options::generate_all_groups_help(String& result) const
{ {
std::vector<std::string> all_groups; generate_group_help(result, m_group);
std::transform(
m_help.begin(),
m_help.end(),
std::back_inserter(all_groups),
[] (const std::map<std::string, HelpGroupDetails>::value_type& group)
{
return group.first;
}
);
generate_group_help(result, all_groups);
} }
inline inline
@ -2799,19 +2890,7 @@ inline
std::vector<std::string> std::vector<std::string>
Options::groups() const Options::groups() const
{ {
std::vector<std::string> g; return m_group;
std::transform(
m_help.begin(),
m_help.end(),
std::back_inserter(g),
[] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
{
return pair.first;
}
);
return g;
} }
inline inline

View File

@ -51,3 +51,10 @@ add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND}
add_executable(link_test link_a.cpp link_b.cpp) add_executable(link_test link_a.cpp link_b.cpp)
target_link_libraries(link_test cxxopts) target_link_libraries(link_test cxxopts)
if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND ("${CMAKE_SYSTEM}" MATCHES "Linux"))
add_executable(fuzzer fuzz.cpp)
target_link_libraries(fuzzer PRIVATE cxxopts)
target_compile_options(fuzzer PRIVATE -fsanitize=fuzzer)
target_link_options(fuzzer PRIVATE -fsanitize=fuzzer)
endif()

File diff suppressed because it is too large Load Diff

107
test/fuzz.cpp Normal file
View File

@ -0,0 +1,107 @@
#include <cassert>
#include <cxxopts.hpp>
#include <fuzzer/FuzzedDataProvider.h>
constexpr int kMaxOptions = 1024;
constexpr int kMaxArgSize = 1024;
enum class ParseableTypes
{
kInt,
kString,
kVectorString,
kFloat,
kDouble,
// Marker for fuzzer.
kMaxValue,
};
template <typename T>
void
add_fuzzed_option(cxxopts::Options* options, FuzzedDataProvider* provider)
{
assert(options);
assert(provider);
options->add_options()(provider->ConsumeRandomLengthString(kMaxArgSize),
provider->ConsumeRandomLengthString(kMaxArgSize),
cxxopts::value<T>());
}
extern "C" int
LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
try
{
FuzzedDataProvider provider(data, size);
// Randomly generate a usage string.
cxxopts::Options options(provider.ConsumeRandomLengthString(kMaxArgSize),
provider.ConsumeRandomLengthString(kMaxArgSize));
// Randomly generate a set of flags configurations.
for (int i = 0; i < provider.ConsumeIntegralInRange<int>(0, kMaxOptions);
i++)
{
switch (provider.ConsumeEnum<ParseableTypes>())
{
case ParseableTypes::kInt:
add_fuzzed_option<int>(&options, &provider);
break;
case ParseableTypes::kString:
add_fuzzed_option<std::string>(&options, &provider);
break;
case ParseableTypes::kVectorString:
add_fuzzed_option<std::vector<std::string>>(&options, &provider);
break;
case ParseableTypes::kFloat:
add_fuzzed_option<float>(&options, &provider);
break;
case ParseableTypes::kDouble:
add_fuzzed_option<double>(&options, &provider);
break;
default:
break;
}
}
// Sometimes allow unrecognised options.
if (provider.ConsumeBool())
{
options.allow_unrecognised_options();
}
// Sometimes allow trailing positional arguments.
if (provider.ConsumeBool())
{
std::string positional_option_name =
provider.ConsumeRandomLengthString(kMaxArgSize);
options.add_options()(positional_option_name,
provider.ConsumeRandomLengthString(kMaxArgSize),
cxxopts::value<std::vector<std::string>>());
options.parse_positional({positional_option_name});
}
// Build command line input.
const int argc = provider.ConsumeIntegralInRange<int>(1, kMaxOptions);
std::vector<std::string> command_line_container;
command_line_container.reserve(argc);
std::vector<const char*> argv;
argv.reserve(argc);
for (int i = 0; i < argc; i++)
{
command_line_container.push_back(
provider.ConsumeRandomLengthString(kMaxArgSize));
argv.push_back(command_line_container[i].c_str());
}
// Parse command line;
auto result = options.parse(argc, argv.data());
} catch (...)
{
}
return 0;
}

View File

@ -112,7 +112,7 @@ TEST_CASE("Basic options", "[options]")
CHECK(arguments[2].key() == "value"); CHECK(arguments[2].key() == "value");
CHECK(arguments[3].key() == "av"); CHECK(arguments[3].key() == "av");
CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value&); CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value);
CHECK(options.program() == "tester"); CHECK(options.program() == "tester");
} }
@ -147,7 +147,7 @@ TEST_CASE("Short options", "[options]")
CHECK(result["c"].as<std::string>() == "foo=something"); CHECK(result["c"].as<std::string>() == "foo=something");
REQUIRE_THROWS_AS(options.add_options()("", "nothing option"), REQUIRE_THROWS_AS(options.add_options()("", "nothing option"),
cxxopts::exceptions::invalid_option_format&); cxxopts::exceptions::invalid_option_format);
} }
TEST_CASE("No positional", "[positional]") TEST_CASE("No positional", "[positional]")
@ -238,9 +238,6 @@ TEST_CASE("No positional with extras", "[positional]")
auto** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto old_argv = argv;
auto old_argc = argc;
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
auto& unmatched = result.unmatched(); auto& unmatched = result.unmatched();
@ -260,7 +257,7 @@ TEST_CASE("Positional not valid", "[positional]") {
auto** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
} }
TEST_CASE("Positional with empty arguments", "[positional]") { TEST_CASE("Positional with empty arguments", "[positional]") {
@ -288,6 +285,37 @@ TEST_CASE("Positional with empty arguments", "[positional]") {
REQUIRE(actual == expected); REQUIRE(actual == expected);
} }
TEST_CASE("Positional with list delimiter", "[positional]") {
std::string single;
std::vector<std::string> positional;
cxxopts::Options options("test_all_positional_list_delimiter", " - test all positional with list delimiters");
options.add_options()
("single", "Single positional param",
cxxopts::value<std::string>(single))
("positional", "Positional parameters vector",
cxxopts::value<std::vector<std::string>>(positional))
;
Argv av({"tester", "a,b", "c,d", "e"});
auto argc = av.argc();
auto argv = av.argv();
std::vector<std::string> pos_names = {"single", "positional"};
options.parse_positional(pos_names.begin(), pos_names.end());
auto result = options.parse(argc, argv);
CHECK(result.unmatched().size() == 0);
REQUIRE(positional.size() == 2);
CHECK(single == "a,b");
CHECK(positional[0] == "c,d");
CHECK(positional[1] == "e");
}
TEST_CASE("Empty with implicit value", "[implicit]") TEST_CASE("Empty with implicit value", "[implicit]")
{ {
cxxopts::Options options("empty_implicit", "doesn't handle empty"); cxxopts::Options options("empty_implicit", "doesn't handle empty");
@ -319,7 +347,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
auto** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::missing_argument&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::missing_argument);
} }
SECTION("With equal-separated true") { SECTION("With equal-separated true") {
@ -489,7 +517,7 @@ TEST_CASE("Unsigned integers", "[options]")
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
} }
TEST_CASE("Integer bounds", "[integer]") TEST_CASE("Integer bounds", "[integer]")
@ -524,14 +552,32 @@ TEST_CASE("Overflow on boundary", "[integer]")
using namespace cxxopts::values; using namespace cxxopts::values;
int8_t si; int8_t si;
int16_t si16;
int64_t si64;
uint8_t ui; uint8_t ui;
uint16_t ui16;
uint64_t ui64;
CHECK_THROWS_AS((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("65536", ui16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("75536", ui16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("32768", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-32769", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-42769", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-75536", si16)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("18446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("28446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("9223372036854775808", si64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-9223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-10223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("-28446744073709551616", si64)), cxxopts::exceptions::incorrect_argument_type);
} }
TEST_CASE("Integer overflow", "[options]") TEST_CASE("Integer overflow", "[options]")
@ -548,11 +594,11 @@ TEST_CASE("Integer overflow", "[options]")
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
int integer = 0; int integer = 0;
CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type);
CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type);
} }
TEST_CASE("Floats", "[options]") TEST_CASE("Floats", "[options]")
@ -593,7 +639,7 @@ TEST_CASE("Invalid integers", "[integer]") {
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
} }
TEST_CASE("Booleans", "[boolean]") { TEST_CASE("Booleans", "[boolean]") {
@ -663,11 +709,13 @@ TEST_CASE("std::vector", "[vector]") {
#ifdef CXXOPTS_HAS_OPTIONAL #ifdef CXXOPTS_HAS_OPTIONAL
TEST_CASE("std::optional", "[optional]") { TEST_CASE("std::optional", "[optional]") {
std::optional<std::string> optional; std::optional<std::string> optional;
std::optional<bool> opt_bool;
cxxopts::Options options("optional", " - tests optional"); cxxopts::Options options("optional", " - tests optional");
options.add_options() options.add_options()
("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional)); ("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional))
("optional_bool", "an boolean optional", cxxopts::value<std::optional<bool>>(opt_bool)->default_value("false"));
Argv av({"optional", "--optional", "foo"}); Argv av({"optional", "--optional", "foo", "--optional_bool", "true"});
auto** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
@ -676,6 +724,8 @@ TEST_CASE("std::optional", "[optional]") {
REQUIRE(optional.has_value()); REQUIRE(optional.has_value());
CHECK(*optional == "foo"); CHECK(*optional == "foo");
CHECK(opt_bool.has_value());
CHECK(*opt_bool);
} }
#endif #endif
@ -699,7 +749,7 @@ TEST_CASE("Unrecognised options", "[options]") {
auto argc = av.argc(); auto argc = av.argc();
SECTION("Default behaviour") { SECTION("Default behaviour") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
} }
SECTION("After allowing unrecognised options") { SECTION("After allowing unrecognised options") {
@ -726,7 +776,7 @@ TEST_CASE("Allow bad short syntax", "[options]") {
auto argc = av.argc(); auto argc = av.argc();
SECTION("Default behaviour") { SECTION("Default behaviour") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
} }
SECTION("After allowing unrecognised options") { SECTION("After allowing unrecognised options") {
@ -749,7 +799,7 @@ TEST_CASE("Invalid option syntax", "[options]") {
auto argc = av.argc(); auto argc = av.argc();
SECTION("Default behaviour") { SECTION("Default behaviour") {
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
} }
} }
@ -768,9 +818,60 @@ TEST_CASE("Options empty", "[options]") {
auto** argv = argv_.argv(); auto** argv = argv_.argv();
CHECK(options.groups().empty()); CHECK(options.groups().empty());
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
} }
#ifdef CXXOPTS_HAS_OPTIONAL
TEST_CASE("Optional value", "[optional]")
{
cxxopts::Options options("options", "query as std::optional");
options.add_options()
("int", "Integer", cxxopts::value<int>())
("float", "Float", cxxopts::value<float>())
("string", "String", cxxopts::value<std::string>())
;
SECTION("Available") {
Argv av({
"--int",
"42",
"--float",
"3.141",
"--string",
"Hello"
});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.as_optional<int>("int"));
CHECK(result.as_optional<float>("float"));
CHECK(result.as_optional<string>("string"));
CHECK(*result.as_optional<int>("int") == 42);
CHECK(*result.as_optional<float>("float") == 3.141);
CHECK(*result.as_optional<string>("string") == "Hello");
}
SECTION("Unavailable") {
Argv av({
});
auto** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(!result.as_optional<int>("int"));
CHECK(!result.as_optional<float>("float"));
CHECK(!result.as_optional<string>("string"));
}
}
#endif
TEST_CASE("Initializer list with group", "[options]") { TEST_CASE("Initializer list with group", "[options]") {
cxxopts::Options options("Initializer list group", " - test initializer list with group"); cxxopts::Options options("Initializer list group", " - test initializer list with group");