aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libbutl/default-options.mxx12
-rw-r--r--libbutl/default-options.txx41
-rw-r--r--tests/default-options/driver.cxx77
-rw-r--r--tests/default-options/testscript24
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