diff options
-rw-r--r-- | libbutl/default-options.mxx | 12 | ||||
-rw-r--r-- | libbutl/default-options.txx | 41 | ||||
-rw-r--r-- | tests/default-options/driver.cxx | 77 | ||||
-rw-r--r-- | tests/default-options/testscript | 24 |
4 files changed, 138 insertions, 16 deletions
diff --git a/libbutl/default-options.mxx b/libbutl/default-options.mxx index 2d44333..1694d48 100644 --- a/libbutl/default-options.mxx +++ b/libbutl/default-options.mxx @@ -11,6 +11,7 @@ #include <utility> // move(), forward(), make_pair() #include <algorithm> // reverse() +#include <stdexcept> // invalid_argument #include <system_error> #endif @@ -107,6 +108,15 @@ LIBBUTL_MODEXPORT namespace butl // // Note that the extra directory options files are never considered remote. // + // For the convenience of implementation, the function parses the option + // files in the reverse order. Thus, to make sure that positions in the + // options list monotonically increase, it needs the maximum number of + // arguments, globally and per file, to be specified. This way the starting + // options position for each file will be less than for the previously + // parsed file by arg_max_file and equal to arg_max - arg_max_file for the + // first file. If the actual number of arguments exceeds the specified, then + // invalid_argument is thrown. + // template <typename O, typename S, typename U, typename F> default_options<O> load_default_options (const optional<dir_path>& sys_dir, @@ -115,6 +125,8 @@ LIBBUTL_MODEXPORT namespace butl const default_options_files&, F&&, const std::string& option, + std::size_t arg_max, + std::size_t arg_max_file, bool args = false); // Merge the default options/arguments and the command line diff --git a/libbutl/default-options.txx b/libbutl/default-options.txx index dc809ad..0c2501c 100644 --- a/libbutl/default-options.txx +++ b/libbutl/default-options.txx @@ -14,10 +14,11 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. throw std::make_pair (path_cast<path> (d), std::move (e)); } - // Search for and parse the options files in the specified directory and - // its local/ subdirectory, if exists, in the reverse order and append the - // options to the resulting list. Return false if --no-default-options is - // encountered. + // Search for and parse the options files in the specified directory and its + // local/ subdirectory, if exists, in the reverse order and append the + // options to the resulting list. Verify that the number of arguments + // doesn't exceed the limits and decrement arg_max by arg_max_file after + // parsing each file. Return false if --no-default-options is encountered. // // Note that by default we check for the local/ subdirectory even if we // don't think it belongs to the remote directory; the user may move things @@ -36,6 +37,8 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. bool remote, const small_vector<path, 2>& fs, F&& fn, + std::size_t& arg_max, + std::size_t arg_max_file, default_options<O>& def_ops, bool load_sub = true, bool load_dir = true) @@ -44,7 +47,7 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. bool r (true); - auto load = [&opt, args, &fs, &fn, &def_ops, &r] + auto load = [&opt, args, &fs, &fn, &def_ops, &arg_max, arg_max_file, &r] (const dir_path& d, bool rem) { using namespace std; @@ -57,9 +60,14 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. { if (file_exists (p)) // Follows symlinks. { + if (arg_max < arg_max_file) + throw invalid_argument ("too many options files"); + + size_t start_pos (arg_max - arg_max_file); + fn (p, rem, false /* overwrite */); - S s (p.string (), opt); + S s (p.string (), opt, start_pos); // @@ Note that the potentially thrown exceptions (unknown option, // unexpected argument, etc) will not contain any location @@ -81,6 +89,15 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. else o.parse (s, U::fail, U::fail); + if (s.position () > arg_max) + throw invalid_argument ("too many options in file " + + p.string ()); + + // Don't decrement arg_max for the empty option files. + // + if (s.position () != start_pos) + arg_max = start_pos; + if (o.no_default_options ()) r = false; @@ -119,6 +136,8 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. const default_options_files& ofs, F&& fn, const std::string& opt, + std::size_t arg_max, + std::size_t arg_max_file, bool args) { if (sys_dir) @@ -214,6 +233,8 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. false /* remote */, ofs.files, std::forward<F> (fn), + arg_max, + arg_max_file, r); load_extra = false; @@ -228,6 +249,8 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. remote, ofs.files, std::forward<F> (fn), + arg_max, + arg_max_file, r, load_build2_local, load_build2); @@ -245,6 +268,8 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. false /* remote */, ofs.files, std::forward<F> (fn), + arg_max, + arg_max_file, r); if (load && home_dir) @@ -258,6 +283,8 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. false /* remote */, ofs.files, std::forward<F> (fn), + arg_max, + arg_max_file, r); } @@ -268,6 +295,8 @@ LIBBUTL_MODEXPORT namespace butl //@@ MOD Clang needs this for some reason. false /* remote */, ofs.files, std::forward<F> (fn), + arg_max, + arg_max_file, r); std::reverse (r.begin (), r.end ()); diff --git a/tests/default-options/driver.cxx b/tests/default-options/driver.cxx index 009cddb..17da19c 100644 --- a/tests/default-options/driver.cxx +++ b/tests/default-options/driver.cxx @@ -4,9 +4,11 @@ #include <cassert> #ifndef __cpp_lib_modules_ts +#include <limits> #include <string> #include <vector> #include <iostream> +#include <exception> #include <stdexcept> // invalid_argument #endif @@ -41,21 +43,21 @@ using namespace butl; // print the resulting options to STDOUT one per line. Note that the options // instance is a vector of arbitrary strings. // -// -f +// -f <file> // Default options file name. Can be specified multiple times. // -// -d +// -d <start-dir> // Directory to start the default options files search from. Can be // specified multiple times, in which case a common start (parent) // directory is deduced. // -// -s +// -s <sys-dir> // System directory. // -// -h +// -h <home-dir> // Home directory. // -// -x +// -x <extra-dir> // Extra directory. // // -a @@ -70,6 +72,12 @@ using namespace butl; // -t // Trace the default options files search to STDERR. // +// -m <num> +// Maximum number of arguments globally (SIZE_MAX/2 by default). +// +// -l <num> +// Maximum number of arguments in the options file (1024 by default). +// int main (int argc, const char* argv[]) { @@ -78,8 +86,8 @@ main (int argc, const char* argv[]) class scanner { public: - scanner (const string& f, const string& option) - : option_ (option) {load (path (f));} + scanner (const string& f, const string& option, size_t pos) + : option_ (option), start_pos_ (pos) {load (path (f));} bool more () @@ -101,6 +109,12 @@ main (int argc, const char* argv[]) return args_[i_++]; } + size_t + position () + { + return start_pos_ + i_; + } + private: void load (const path& f) @@ -133,6 +147,7 @@ main (int argc, const char* argv[]) optional<string> option_; vector<string> args_; size_t i_ = 0; + size_t start_pos_; }; enum class unknow_mode @@ -141,6 +156,15 @@ main (int argc, const char* argv[]) fail }; + class unknown_argument: public std::exception + { + public: + string argument; + + explicit + unknown_argument (string a): argument (move (a)) {} + }; + class options: public vector<string> { public: @@ -157,7 +181,7 @@ main (int argc, const char* argv[]) switch (m) { case unknow_mode::stop: return r; - case unknow_mode::fail: throw invalid_argument (a); + case unknow_mode::fail: throw unknown_argument (move (a)); } } @@ -200,6 +224,23 @@ main (int argc, const char* argv[]) vector<string> cmd_args; bool print_entries (false); bool trace (false); + size_t arg_max (numeric_limits<size_t>::max () / 2); + size_t arg_max_file (1024); + + auto num = [] (const string& s) -> size_t + { + assert (!s.empty ()); + + char* e (nullptr); + errno = 0; // We must clear it according to POSIX. + uint64_t r (strtoull (s.c_str (), &e, 10)); // Can't throw. + + assert (errno != ERANGE && + e == s.c_str () + s.size () && + r <= numeric_limits<size_t>::max ()); + + return static_cast<size_t> (r); + }; for (int i (1); i != argc; ++i) { @@ -242,6 +283,16 @@ main (int argc, const char* argv[]) { trace = true; } + else if (a == "-m") + { + assert (++i != argc); + arg_max = num (argv[i]); + } + else if (a == "-l") + { + assert (++i != argc); + arg_max_file = num (argv[i]); + } else if (a.compare (0, 2, "--") == 0) cmd_ops.push_back (move (a)); else @@ -270,11 +321,19 @@ main (int argc, const char* argv[]) << (remote ? "remote " : "local ") << f << endl; }, "--options-file", + arg_max, + arg_max_file, args); } + catch (const unknown_argument& e) + { + cerr << "error: unexpected argument '" << e.argument << "'" << endl; + return 1; + } catch (const invalid_argument& e) { - cerr << "error: unexpected argument '" << e.what () << "'" << endl; + cerr << "error: unable to load default options files: " << e.what () + << endl; return 1; } diff --git a/tests/default-options/testscript b/tests/default-options/testscript index 09bb2ec..f071701 100644 --- a/tests/default-options/testscript +++ b/tests/default-options/testscript @@ -51,6 +51,7 @@ +mkdir -p $d/local/ +echo '--package-foo' >=$d/foo + +echo '--package-fox' >+$d/foo +echo '--package-bar' >=$d/bar +echo '--package-local-foo' >=$d/local/foo +echo '--package-local-bar' >=$d/local/bar @@ -78,7 +79,7 @@ %\.+/home/work/project/.build2/bar,--project-bar,true% %\.+/home/work/project/.build2/local/foo,--project-local-foo,true% %\.+/home/work/project/.build2/local/bar,--project-local-bar,true% - %\.+/home/work/project/package/.build2/foo,--package-foo,true% + %\.+/home/work/project/package/.build2/foo,--package-foo --package-fox,true% %\.+/home/work/project/package/.build2/bar,--package-bar,true% %\.+/home/work/project/package/.build2/local/foo,--package-local-foo,true% %\.+/home/work/project/package/.build2/local/bar,--package-local-bar,true% @@ -129,6 +130,7 @@ --project-local-foo --project-local-bar --package-foo + --package-fox --package-bar --package-local-foo --package-local-bar @@ -150,6 +152,26 @@ %overwriting remote \.+/home/work/project/package/.build2/local/baz% %overwriting remote \.+/home/work/project/package/.build2/local/foo% EOE + + : positions + : + { + : success + : + $* -f foo -f bar -d $start_dir -m 36 -l 2 >! + + : fail-file + : + $* -f foo -f bar -d $start_dir -m 36 -l 1 2>>/~%EOE% != 0 + %error: unable to load default options files: too many options in file .+/package/\.build2/foo% + EOE + + : fail-globally + : + $* -f foo -f bar -d $start_dir -m 100 -l 10 2>>EOE != 0 + error: unable to load default options files: too many options files + EOE + } } : args |