]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Add an Args class with backward compatibility for the old args API (#575)
authorJoel Rosdahl <joel@rosdahl.net>
Tue, 7 Apr 2020 19:54:30 +0000 (21:54 +0200)
committerGitHub <noreply@github.com>
Tue, 7 Apr 2020 19:54:30 +0000 (21:54 +0200)
The idea of this is to make a proper C++ API while at the same time
implementing most of the old legacy args API. This makes it possible to
refactor call sites gradually instead of in a series of large
refactoring steps.

Given that “struct args* args” is replaced with “Args args”, the
following types of legacy API usage can be kept without source changes:

1. The args_* functions can be used as is.
2. args->argv[i] still works for read access.

Things that aren’t emulated:

1. args->argc (use args.size() instead).
2. args->argv to access the underlying char* array (use
   args.to_argv().data() instead).
3. Mutating the args->argv[i] string in place (don’t do that).

In other words, code that uses args_foo(args, ...) and args->argv[i] can
easily be converted to args.bar(...) and args[i] (with std::string
arguments instead of char*) at will and in arbitrarily small steps.

19 files changed:
Makefile.in
configure.ac
src/Args.cpp [new file with mode: 0644]
src/Args.hpp [new file with mode: 0644]
src/ArgsInfo.cpp
src/ArgsInfo.hpp
src/Context.cpp
src/Context.hpp
src/args.cpp [deleted file]
src/args.hpp [deleted file]
src/ccache.cpp
src/ccache.hpp
src/hashutil.cpp
unittest/framework.cpp
unittest/framework.hpp
unittest/test_Args.cpp [new file with mode: 0644]
unittest/test_args.cpp [deleted file]
unittest/test_argument_processing.cpp
unittest/test_legacy_args.cpp [new file with mode: 0644]

index fb070555dfe88efb0b8478755238014a29b9b846..a30ed7f918c165e05e43180b24fdc09e3feed739 100644 (file)
@@ -31,6 +31,7 @@ quiet := $(v_at_$(V))
 Q=$(if $(quiet),@)
 
 non_third_party_sources = \
+    src/Args.cpp \
     src/ArgsInfo.cpp \
     src/AtomicFile.cpp \
     src/CacheEntryReader.cpp \
@@ -50,7 +51,6 @@ non_third_party_sources = \
     src/Util.cpp \
     src/ZstdCompressor.cpp \
     src/ZstdDecompressor.cpp \
-    src/args.cpp \
     src/ccache.cpp \
     src/cleanup.cpp \
     src/compopt.cpp \
@@ -81,6 +81,7 @@ non_third_party_objs = $(patsubst %.c, %.o, $(patsubst %.cpp, %.o, $(non_third_p
 ccache_sources = src/main.cpp $(base_sources)
 ccache_objs = $(patsubst %.c, %.o, $(patsubst %.cpp, %.o, $(ccache_sources)))
 
+test_suites += unittest/test_Args.cpp
 test_suites += unittest/test_AtomicFile.cpp
 test_suites += unittest/test_Checksum.cpp
 test_suites += unittest/test_Compression.cpp
@@ -92,11 +93,11 @@ test_suites += unittest/test_ScopeGuard.cpp
 test_suites += unittest/test_Stat.cpp
 test_suites += unittest/test_Util.cpp
 test_suites += unittest/test_ZstdCompression.cpp
-test_suites += unittest/test_args.cpp
 test_suites += unittest/test_argument_processing.cpp
 test_suites += unittest/test_compopt.cpp
 test_suites += unittest/test_hash.cpp
 test_suites += unittest/test_hashutil.cpp
+test_suites += unittest/test_legacy_args.cpp
 test_suites += unittest/test_legacy_util.cpp
 
 test_sources += unittest/catch2_tests.cpp
index 36b9b29e9e8baed65ce6fc08cc4c52aeaded22db..1b2bf291927e630e84bd1ba04a6a93ff7270fcb6 100644 (file)
@@ -93,6 +93,7 @@ if test "$dev_mode" = yes -o "$enable_more_warnings" = yes; then
         AX_CHECK_COMPILE_FLAG([-Wno-global-constructors], [extra_warnings="$extra_warnings -Wno-global-constructors"],, [-Werror])
         AX_CHECK_COMPILE_FLAG([-Wno-implicit-fallthrough], [extra_warnings="$extra_warnings -Wno-implicit-fallthrough"],, [-Werror])
         AX_CHECK_COMPILE_FLAG([-Wno-padded], [extra_warnings="$extra_warnings -Wno-padded"],, [-Werror])
+        AX_CHECK_COMPILE_FLAG([-Wno-return-std-move-in-c++11], [extra_warnings="$extra_warnings -Wno-return-std-move-in-c++11"],, [-Werror])
         AX_CHECK_COMPILE_FLAG([-Wno-shadow-field-in-constructor], [extra_warnings="$extra_warnings -Wno-shadow-field-in-constructor"],, [-Werror])
         AX_CHECK_COMPILE_FLAG([-Wno-shorten-64-to-32], [extra_warnings="$extra_warnings -Wno-shorten-64-to-32"],, [-Werror])
         AX_CHECK_COMPILE_FLAG([-Wno-sign-conversion], [extra_warnings="$extra_warnings -Wno-sign-conversion"],, [-Werror])
diff --git a/src/Args.cpp b/src/Args.cpp
new file mode 100644 (file)
index 0000000..ae2b598
--- /dev/null
@@ -0,0 +1,322 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "Args.hpp"
+
+Args::Args() : argv(m_args)
+{
+}
+
+Args::Args(const Args& other) : m_args(other.m_args), argv(m_args)
+{
+}
+
+Args::Args(Args&& other) : m_args(std::move(other.m_args)), argv(m_args)
+{
+}
+
+Args
+Args::from_argv(int argc, const char* const* argv)
+{
+  Args args;
+  args.m_args.assign(argv, argv + argc);
+  return args;
+}
+
+Args
+Args::from_string(const std::string& command)
+{
+  char* p = x_strdup(command.c_str());
+  char* q = p;
+  char* word;
+  char* saveptr = NULL;
+  Args args;
+  while ((word = strtok_r(q, " \t\r\n", &saveptr))) {
+    args.push_back(word);
+    q = NULL;
+  }
+
+  free(p);
+  return args;
+}
+
+nonstd::optional<Args>
+Args::from_gcc_atfile(const std::string& filename)
+{
+  std::string argtext;
+  try {
+    argtext = Util::read_file(filename);
+  } catch (Error&) {
+    return nonstd::nullopt;
+  }
+
+  Args args;
+  auto pos = argtext.cbegin();
+  std::string argbuf;
+  argbuf.resize(argtext.length() + 1);
+  auto argpos = argbuf.begin();
+
+  // Used to track quoting state; if \0 we are not inside quotes. Otherwise
+  // stores the quoting character that started it for matching the end quote.
+  char quoting = '\0';
+
+  while (true) {
+    switch (*pos) {
+    case '\\':
+      pos++;
+      if (*pos == '\0') {
+        continue;
+      }
+      break;
+
+    case '"':
+    case '\'':
+      if (quoting != '\0') {
+        if (quoting == *pos) {
+          quoting = '\0';
+          pos++;
+          continue;
+        } else {
+          break;
+        }
+      } else {
+        quoting = *pos;
+        pos++;
+        continue;
+      }
+
+    case '\n':
+    case '\r':
+    case '\t':
+    case ' ':
+      if (quoting) {
+        break;
+      }
+      // Fall through.
+
+    case '\0':
+      // End of token
+      *argpos = '\0';
+      if (argbuf[0] != '\0') {
+        args_add(args, argbuf);
+      }
+      argpos = argbuf.begin();
+      if (*pos == '\0') {
+        return args;
+      } else {
+        pos++;
+        continue;
+      }
+    }
+
+    *argpos = *pos;
+    pos++;
+    argpos++;
+  }
+}
+
+Args&
+Args::operator=(const Args& other)
+{
+  m_args = other.m_args;
+  argv.m_args = &m_args;
+  return *this;
+}
+
+Args&
+Args::operator=(Args&& other)
+{
+  m_args = std::move(other.m_args);
+  argv.m_args = &m_args;
+  return *this;
+}
+
+std::vector<const char*>
+Args::to_argv() const
+{
+  std::vector<const char*> result;
+  result.reserve(m_args.size() + 1);
+  for (const auto& arg : m_args) {
+    result.push_back(arg.c_str());
+  }
+  result.push_back(nullptr);
+  return result;
+}
+
+std::string
+Args::to_string() const
+{
+  std::string result;
+  for (const auto& arg : m_args) {
+    if (!result.empty()) {
+      result += ' ';
+    }
+    result += arg;
+  }
+  return result;
+}
+
+void
+Args::erase_with_prefix(nonstd::string_view prefix)
+{
+  m_args.erase(std::remove_if(m_args.begin(),
+                              m_args.end(),
+                              [&prefix](const std::string& s) {
+                                return Util::starts_with(s, prefix);
+                              }),
+               m_args.end());
+}
+
+void
+Args::insert(size_t index, const Args& args)
+{
+  if (args.size() == 0) {
+    return;
+  }
+  m_args.insert(m_args.begin() + index, args.m_args.begin(), args.m_args.end());
+}
+
+void
+Args::pop_back(size_t count)
+{
+  m_args.erase(m_args.end() - count, m_args.end());
+}
+
+void
+Args::pop_front(size_t count)
+{
+  m_args.erase(m_args.begin(), m_args.begin() + count);
+}
+
+void
+Args::push_back(const std::string& arg)
+{
+  m_args.push_back(arg);
+}
+
+void
+Args::push_back(const Args& args)
+{
+  m_args.insert(m_args.end(), args.m_args.begin(), args.m_args.end());
+}
+
+void
+Args::push_front(const std::string& arg)
+{
+  m_args.push_front(arg);
+}
+
+void
+Args::replace(size_t index, const Args& args)
+{
+  if (args.size() == 1) {
+    // Trivial case; replace with 1 element.
+    m_args[index] = args[0];
+  } else {
+    m_args.erase(m_args.begin() + index);
+    insert(index, args);
+  }
+}
+
+Args::ArgvAccessWrapper::ArgvAccessWrapper(const std::deque<std::string>& args)
+  : m_args(&args)
+{
+}
+
+const char* Args::ArgvAccessWrapper::operator[](size_t i) const
+{
+  return i == m_args->size() ? nullptr : m_args->at(i).c_str();
+}
+
+// === Wrapper functions for the legacy API: ===
+
+void
+args_add(Args& args, const std::string& arg)
+{
+  args.push_back(arg);
+}
+
+void
+args_add_prefix(Args& args, const std::string& arg)
+{
+  args.push_front(arg);
+}
+
+Args
+args_copy(const Args& args)
+{
+  return args;
+}
+
+void
+args_extend(Args& args, const Args& to_append)
+{
+  args.push_back(to_append);
+}
+
+Args
+args_init(int argc, const char* const* argv)
+{
+  return Args::from_argv(argc, argv);
+}
+
+nonstd::optional<Args>
+args_init_from_gcc_atfile(const std::string& filename)
+{
+  return Args::from_gcc_atfile(filename);
+}
+
+Args
+args_init_from_string(const std::string& s)
+{
+  return Args::from_string(s);
+}
+
+void
+args_insert(Args& args, size_t index, const Args& to_insert, bool replace)
+{
+  if (replace) {
+    args.replace(index, to_insert);
+  } else {
+    args.insert(index, to_insert);
+  }
+}
+
+void
+args_pop(Args& args, size_t count)
+{
+  args.pop_back(count);
+}
+
+void
+args_remove_first(Args& args)
+{
+  args.pop_front(1);
+}
+
+void
+args_set(Args& args, size_t index, const std::string& value)
+{
+  args[index] = value;
+}
+
+void
+args_strip(Args& args, nonstd::string_view prefix)
+{
+  args.erase_with_prefix(prefix);
+}
diff --git a/src/Args.hpp b/src/Args.hpp
new file mode 100644 (file)
index 0000000..b87424f
--- /dev/null
@@ -0,0 +1,161 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#pragma once
+
+#include "system.hpp"
+
+#include "NonCopyable.hpp"
+#include "Util.hpp"
+
+#include "third_party/nonstd/optional.hpp"
+#include "third_party/nonstd/string_view.hpp"
+
+#include <deque>
+#include <string>
+
+class Args
+{
+public:
+  Args();
+  Args(const Args& other);
+  Args(Args&& other);
+
+  static Args from_argv(int argc, const char* const* argv);
+  static Args from_string(const std::string& command);
+  static nonstd::optional<Args> from_gcc_atfile(const std::string& filename);
+
+  Args& operator=(const Args& other);
+  Args& operator=(Args&& other);
+
+  bool operator==(const Args& other) const;
+  bool operator!=(const Args& other) const;
+
+  size_t size() const;
+  const std::string& operator[](size_t i) const;
+  std::string& operator[](size_t i);
+
+  // Accessor functions for the legacy API:
+  Args& operator*();
+  const Args* operator->() const;
+
+  // Return the argument list as a vector of raw string pointers. Callers can
+  // use `const_cast<char* const*>(args.to_argv().data())` to get an array
+  // suitable to pass to e.g. execv(2).
+  std::vector<const char*> to_argv() const;
+
+  // Return a space-delimited argument list in string form. No quoting of spaces
+  // in arguments is performed.
+  std::string to_string() const;
+
+  // Remove all arguments with prefix `prefix`.
+  void erase_with_prefix(nonstd::string_view prefix);
+
+  // Insert arguments in `args` at position `index`.
+  void insert(size_t index, const Args& args);
+
+  // Remove the last `count` arguments.
+  void pop_back(size_t count = 1);
+
+  // Remove the first `count` arguments.
+  void pop_front(size_t count = 1);
+
+  // Add `arg` to the end.
+  void push_back(const std::string& arg);
+
+  // Add `args` to the end.
+  void push_back(const Args& args);
+
+  // Add `arg` to the front.
+  void push_front(const std::string& arg);
+
+  // Replace the argument at `index` with all arguments in `args`.
+  void replace(size_t index, const Args& args);
+
+private:
+  std::deque<std::string> m_args;
+
+public:
+  // Wrapper for legacy API:
+  class ArgvAccessWrapper
+  {
+  public:
+    friend Args;
+
+    ArgvAccessWrapper(const std::deque<std::string>& args);
+
+    const char* operator[](size_t i) const;
+
+  private:
+    const std::deque<std::string>* m_args;
+  };
+
+  ArgvAccessWrapper argv;
+};
+
+inline bool
+Args::operator==(const Args& other) const
+{
+  return m_args == other.m_args;
+}
+
+inline bool
+Args::operator!=(const Args& other) const
+{
+  return m_args != other.m_args;
+}
+
+inline size_t
+Args::size() const
+{
+  return m_args.size();
+}
+
+inline const std::string& Args::operator[](size_t i) const
+{
+  return m_args[i];
+}
+
+inline std::string& Args::operator[](size_t i)
+{
+  return m_args[i];
+}
+
+inline Args& Args::operator*()
+{
+  return *this;
+}
+
+inline const Args* Args::operator->() const
+{
+  return this;
+}
+
+// Wrapper functions for the legacy API:
+void args_add(Args& args, const std::string& arg);
+void args_add_prefix(Args& args, const std::string& arg);
+Args args_copy(const Args& args);
+void args_extend(Args& args, const Args& to_append);
+Args args_init(int argc, const char* const* argv);
+nonstd::optional<Args> args_init_from_gcc_atfile(const std::string& filename);
+Args args_init_from_string(const std::string& s);
+void args_insert(Args& args, size_t index, const Args& to_insert, bool replace);
+void args_pop(Args& args, size_t count);
+void args_remove_first(Args& args);
+void args_set(Args& args, size_t index, const std::string& value);
+void args_strip(Args& args, nonstd::string_view prefix);
index 7d20bd0c136e9648491968473462bb1bf008d6dd..180dafb3054576f8827b165f2229b1c5a76a882f 100644 (file)
 
 #include "ArgsInfo.hpp"
 
-#include "args.hpp"
-
 ArgsInfo::~ArgsInfo()
 {
   for (size_t i = 0; i < debug_prefix_maps_len; i++) {
     free(debug_prefix_maps[i]);
   }
   free(debug_prefix_maps);
-
-  args_free(depend_extra_args);
 }
index 39aea7e390dbddbe96ed8144d09dddce6f1c367f..76a9a708ab78df3775658335820adc6ba3f37484 100644 (file)
@@ -20,6 +20,8 @@
 
 #include "system.hpp"
 
+#include "Args.hpp"
+
 #include <string>
 
 // This class holds meta-information derived from the compiler arguments.
@@ -102,7 +104,7 @@ struct ArgsInfo
   size_t debug_prefix_maps_len = 0;
 
   // Argument list to add to compiler invocation in depend mode.
-  struct args* depend_extra_args = nullptr;
+  Args depend_extra_args;
 
   ArgsInfo() = default;
   ~ArgsInfo();
index aaaff793000f41dc90a946935d3e905ee3b1d3b7..9401c37d9831ab6cfd7eb10ef4f8264b93981617 100644 (file)
@@ -20,7 +20,6 @@
 
 #include "Counters.hpp"
 #include "Util.hpp"
-#include "args.hpp"
 
 Context::Context()
   : actual_cwd(Util::get_actual_cwd()),
@@ -30,8 +29,6 @@ Context::Context()
 
 Context::~Context()
 {
-  args_free(orig_args);
-
   free(result_name);
 
   for (size_t i = 0; i < ignore_headers_len; i++) {
index 7b122ad20a3d75d161d9b06c8668ecbb9d0b1421..d492d58af3c25a37aa2df759dde837f1c267d2b1 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "system.hpp"
 
+#include "Args.hpp"
 #include "ArgsInfo.hpp"
 #include "Config.hpp"
 #include "NonCopyable.hpp"
@@ -28,8 +29,6 @@
 
 #include <unordered_map>
 
-struct args;
-
 struct Context : NonCopyable
 {
   Context();
@@ -45,7 +44,7 @@ struct Context : NonCopyable
   std::string apparent_cwd;
 
   // The original argument list.
-  struct args* orig_args = nullptr;
+  Args orig_args;
 
   // Name (represented as a struct digest) of the file containing the cached
   // result.
diff --git a/src/args.cpp b/src/args.cpp
deleted file mode 100644 (file)
index 85fcfac..0000000
+++ /dev/null
@@ -1,321 +0,0 @@
-// Copyright (C) 2002 Andrew Tridgell
-// Copyright (C) 2009-2020 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation; either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// this program; if not, write to the Free Software Foundation, Inc., 51
-// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-#include "args.hpp"
-
-#include "legacy_util.hpp"
-
-struct args*
-args_init(int init_argc, const char* const* init_args)
-{
-  struct args* args = (struct args*)x_malloc(sizeof(struct args));
-  args->argc = 0;
-  args->argv = (char**)x_malloc(sizeof(char*));
-  args->argv[0] = nullptr;
-  for (int i = 0; i < init_argc; i++) {
-    args_add(args, init_args[i]);
-  }
-  return args;
-}
-
-struct args*
-args_init_from_string(const char* command)
-{
-  char* p = x_strdup(command);
-  char* q = p;
-  char *word, *saveptr = nullptr;
-  struct args* args = args_init(0, nullptr);
-  while ((word = strtok_r(q, " \t\r\n", &saveptr))) {
-    args_add(args, word);
-    q = nullptr;
-  }
-
-  free(p);
-  return args;
-}
-
-struct args*
-args_init_from_gcc_atfile(const char* filename)
-{
-  char* argtext;
-  if (!(argtext = read_text_file(filename, 0))) {
-    return nullptr;
-  }
-
-  struct args* args = args_init(0, nullptr);
-  char* pos = argtext;
-  char* argbuf = static_cast<char*>(x_malloc(strlen(argtext) + 1));
-  char* argpos = argbuf;
-
-  // Used to track quoting state; if \0, we are not inside quotes. Otherwise
-  // stores the quoting character that started it, for matching the end quote.
-  char quoting = '\0';
-
-  while (1) {
-    switch (*pos) {
-    case '\\':
-      pos++;
-      if (*pos == '\0') {
-        continue;
-      }
-      break;
-
-    case '\"':
-    case '\'':
-      if (quoting != '\0') {
-        if (quoting == *pos) {
-          quoting = '\0';
-          pos++;
-          continue;
-        } else {
-          break;
-        }
-      } else {
-        quoting = *pos;
-        pos++;
-        continue;
-      }
-
-    case '\n':
-    case '\r':
-    case '\t':
-    case ' ':
-      if (quoting) {
-        break;
-      }
-      // Fall through.
-
-    case '\0':
-      // End of token
-      *argpos = '\0';
-      if (argbuf[0] != '\0') {
-        args_add(args, argbuf);
-      }
-      argpos = argbuf;
-      if (*pos == '\0') {
-        goto out;
-      } else {
-        pos++;
-        continue;
-      }
-    }
-
-    *argpos = *pos;
-    pos++;
-    argpos++;
-  }
-
-out:
-  free(argbuf);
-  free(argtext);
-  return args;
-}
-
-struct args*
-args_copy(struct args* args)
-{
-  return args_init(args->argc, args->argv);
-}
-
-// Insert all arguments in src into dest at position index. If replace is true,
-// the element at dest->argv[index] is replaced with the contents of src and
-// everything past it is shifted. Otherwise, dest->argv[index] is also shifted.
-//
-// src is consumed by this operation and should not be freed or used again by
-// the caller.
-void
-args_insert(struct args* dest, int index, struct args* src, bool replace)
-{
-  // Adjustments made if we are replacing or shifting the element currently at
-  // dest->argv[index].
-  int offset = replace ? 1 : 0;
-
-  if (replace) {
-    free(dest->argv[index]);
-  }
-
-  if (src->argc == 0) {
-    if (replace) {
-      // Have to shift everything down by 1 since we replaced with an empty
-      // list.
-      for (int i = index; i < dest->argc; i++) {
-        dest->argv[i] = dest->argv[i + 1];
-      }
-      dest->argc--;
-    }
-    args_free(src);
-    return;
-  }
-
-  if (src->argc == 1 && replace) {
-    // Trivial case; replace with 1 element.
-    dest->argv[index] = src->argv[0];
-    src->argc = 0;
-    args_free(src);
-    return;
-  }
-
-  dest->argv = (char**)x_realloc(
-    dest->argv, (src->argc + dest->argc + 1 - offset) * sizeof(char*));
-
-  // Shift arguments over.
-  for (int i = dest->argc; i >= index + offset; i--) {
-    dest->argv[i + src->argc - offset] = dest->argv[i];
-  }
-
-  // Copy the new arguments into place.
-  for (int i = 0; i < src->argc; i++) {
-    dest->argv[i + index] = src->argv[i];
-  }
-
-  dest->argc += src->argc - offset;
-  src->argc = 0;
-  args_free(src);
-}
-
-void
-args_free(struct args* args)
-{
-  if (!args) {
-    return;
-  }
-  for (int i = 0; i < args->argc; ++i) {
-    if (args->argv[i]) {
-      free(args->argv[i]);
-    }
-  }
-  free(args->argv);
-  free(args);
-}
-
-void
-args_add(struct args* args, const char* s)
-{
-  args->argv = (char**)x_realloc(args->argv, (args->argc + 2) * sizeof(char*));
-  args->argv[args->argc] = x_strdup(s);
-  args->argc++;
-  args->argv[args->argc] = nullptr;
-}
-
-// Add all arguments in to_append to args.
-void
-args_extend(struct args* args, struct args* to_append)
-{
-  for (int i = 0; i < to_append->argc; i++) {
-    args_add(args, to_append->argv[i]);
-  }
-}
-
-// Pop the last element off the args list.
-void
-args_pop(struct args* args, int n)
-{
-  while (n--) {
-    args->argc--;
-    free(args->argv[args->argc]);
-    args->argv[args->argc] = nullptr;
-  }
-}
-
-// Set argument at given index.
-void
-args_set(struct args* args, int index, const char* value)
-{
-  assert(index < args->argc);
-  free(args->argv[index]);
-  args->argv[index] = x_strdup(value);
-}
-
-// Remove the first element of the argument list.
-void
-args_remove_first(struct args* args)
-{
-  free(args->argv[0]);
-  memmove(&args->argv[0], &args->argv[1], args->argc * sizeof(args->argv[0]));
-  args->argc--;
-}
-
-// Add an argument into the front of the argument list.
-void
-args_add_prefix(struct args* args, const char* s)
-{
-  args->argv = (char**)x_realloc(args->argv, (args->argc + 2) * sizeof(char*));
-  memmove(
-    &args->argv[1], &args->argv[0], (args->argc + 1) * sizeof(args->argv[0]));
-  args->argv[0] = x_strdup(s);
-  args->argc++;
-}
-
-// Strip any arguments beginning with the specified prefix.
-void
-args_strip(struct args* args, const char* prefix)
-{
-  for (int i = 0; i < args->argc;) {
-    if (str_startswith(args->argv[i], prefix)) {
-      free(args->argv[i]);
-      memmove(&args->argv[i],
-              &args->argv[i + 1],
-              (args->argc - i) * sizeof(args->argv[i]));
-      args->argc--;
-    } else {
-      i++;
-    }
-  }
-}
-
-// Format args to a space-separated string. Does not quote spaces. Caller
-// frees.
-char*
-args_to_string(const struct args* args)
-{
-  unsigned size = 0;
-  for (char** p = args->argv; *p; p++) {
-    size += strlen(*p) + 1;
-  }
-
-  char* result = static_cast<char*>(x_malloc(size + 1));
-  int pos = 0;
-  for (char** p = args->argv; *p; p++) {
-    pos += sprintf(&result[pos], "%s ", *p);
-  }
-  result[pos - 1] = '\0';
-  return result;
-}
-
-// Returns true if args1 equals args2, else false.
-bool
-args_equal(const struct args* args1, const struct args* args2)
-{
-  if (args1->argc != args2->argc) {
-    return false;
-  }
-  for (int i = 0; i < args1->argc; i++) {
-    if (!str_eq(args1->argv[i], args2->argv[i])) {
-      return false;
-    }
-  }
-  return true;
-}
-
-void
-args_deleter::operator()(struct args*& arg)
-{
-  args_free(arg);
-  arg = nullptr;
-}
diff --git a/src/args.hpp b/src/args.hpp
deleted file mode 100644 (file)
index bf5bb93..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (C) 2020 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation; either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// this program; if not, write to the Free Software Foundation, Inc., 51
-// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-#pragma once
-
-#include "ScopeGuard.hpp"
-
-struct args
-{
-  char** argv;
-  int argc;
-};
-
-struct args* args_init(int, const char* const*);
-struct args* args_init_from_string(const char*);
-struct args* args_init_from_gcc_atfile(const char* filename);
-struct args* args_copy(struct args* args);
-void args_free(struct args* args);
-void args_add(struct args* args, const char* s);
-void args_add_prefix(struct args* args, const char* s);
-void args_extend(struct args* args, struct args* to_append);
-void args_insert(struct args* dest, int index, struct args* src, bool replace);
-void args_pop(struct args* args, int n);
-void args_set(struct args* args, int index, const char* value);
-void args_strip(struct args* args, const char* prefix);
-void args_remove_first(struct args* args);
-char* args_to_string(const struct args* args);
-bool args_equal(const struct args* args1, const struct args* args2);
-
-struct args_deleter
-{
-  void operator()(struct args*& arg);
-};
-using ArgsScopeGuard = ScopeGuard<args_deleter, struct args*>;
index 12127380d726dd1bfa95d725122f19af73340f29..2129209ad2acc79ca10ff38999a24a2a53ed2178 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "ccache.hpp"
 
+#include "Args.hpp"
 #include "ArgsInfo.hpp"
 #include "Context.hpp"
 #include "File.hpp"
@@ -26,7 +27,6 @@
 #include "ProgressBar.hpp"
 #include "ScopeGuard.hpp"
 #include "Util.hpp"
-#include "args.hpp"
 #include "cleanup.hpp"
 #include "compopt.hpp"
 #include "compress.hpp"
@@ -159,13 +159,13 @@ static pid_t compiler_pid = 0;
 static const char HASH_PREFIX[] = "3";
 
 static void
-add_prefix(const Context& ctx, struct args* args, const char* prefix_command)
+add_prefix(const Context& ctx, Args& args, const char* prefix_command)
 {
   if (str_eq(prefix_command, "")) {
     return;
   }
 
-  struct args* prefix = args_init(0, nullptr);
+  Args prefix;
   char* e = x_strdup(prefix_command);
   char* saveptr = nullptr;
   for (char* tok = strtok_r(e, " ", &saveptr); tok;
@@ -183,10 +183,9 @@ add_prefix(const Context& ctx, struct args* args, const char* prefix_command)
   free(e);
 
   cc_log("Using command-line prefix %s", prefix_command);
-  for (int i = prefix->argc; i != 0; i--) {
+  for (size_t i = prefix.size(); i != 0; i--) {
     args_add_prefix(args, prefix->argv[i - 1]);
   }
-  args_free(prefix);
 }
 
 // If `exit_code` is set, just exit with that code directly, otherwise execute
@@ -1108,8 +1107,8 @@ create_cachedir_tag(nonstd::string_view dir)
 // Run the real compiler and put the result in cache.
 static void
 to_cache(Context& ctx,
-         struct args* args,
-         struct args* depend_extra_args,
+         Args& args,
+         Args& depend_extra_args,
          struct hash* depend_mode_hash)
 {
   args_add(args, "-o");
@@ -1165,7 +1164,8 @@ to_cache(Context& ctx,
     tmp_stdout_fd = create_tmp_fd(&tmp_stdout);
     tmp_stderr = format("%s/tmp.stderr", temp_dir(ctx));
     tmp_stderr_fd = create_tmp_fd(&tmp_stderr);
-    status = execute(args->argv, tmp_stdout_fd, tmp_stderr_fd, &compiler_pid);
+    status = execute(
+      args.to_argv().data(), tmp_stdout_fd, tmp_stderr_fd, &compiler_pid);
     args_pop(args, 3);
   } else {
     // The cached result path is not known yet, use temporary files.
@@ -1176,18 +1176,16 @@ to_cache(Context& ctx,
 
     // Use the original arguments (including dependency options) in depend
     // mode.
-    assert(ctx.orig_args);
-    struct args* depend_mode_args = args_copy(ctx.orig_args);
+    Args depend_mode_args = ctx.orig_args;
     args_strip(depend_mode_args, "--ccache-");
-    if (depend_extra_args) {
-      args_extend(depend_mode_args, depend_extra_args);
-    }
+    args_extend(depend_mode_args, depend_extra_args);
     add_prefix(ctx, depend_mode_args, ctx.config.prefix_command().c_str());
 
     ctx.time_of_compilation = time(nullptr);
-    status = execute(
-      depend_mode_args->argv, tmp_stdout_fd, tmp_stderr_fd, &compiler_pid);
-    args_free(depend_mode_args);
+    status = execute(depend_mode_args.to_argv().data(),
+                     tmp_stdout_fd,
+                     tmp_stderr_fd,
+                     &compiler_pid);
   }
   MTR_END("execute", "compiler");
 
@@ -1368,7 +1366,7 @@ to_cache(Context& ctx,
 // Find the result name by running the compiler in preprocessor mode and
 // hashing the result.
 static struct digest*
-get_result_name_from_cpp(Context& ctx, struct args* args, struct hash* hash)
+get_result_name_from_cpp(Context& ctx, Args& args, struct hash* hash)
 {
   ctx.time_of_compilation = time(nullptr);
 
@@ -1406,7 +1404,8 @@ get_result_name_from_cpp(Context& ctx, struct args* args, struct hash* hash)
     add_prefix(ctx, args, ctx.config.prefix_command_cpp().c_str());
     cc_log("Running preprocessor");
     MTR_BEGIN("execute", "preprocessor");
-    status = execute(args->argv, path_stdout_fd, path_stderr_fd, &compiler_pid);
+    status = execute(
+      args.to_argv().data(), path_stdout_fd, path_stderr_fd, &compiler_pid);
     MTR_END("execute", "preprocessor");
     args_pop(args, args_added);
   }
@@ -1545,7 +1544,7 @@ hash_nvcc_host_compiler(const Context& ctx,
 // Update a hash with information common for the direct and preprocessor modes.
 static void
 hash_common_info(const Context& ctx,
-                 struct args* args,
+                 const Args& args,
                  struct hash* hash,
                  const ArgsInfo& args_info)
 {
@@ -1689,8 +1688,8 @@ hash_common_info(const Context& ctx,
 // otherwise NULL. Caller frees.
 static struct digest*
 calculate_result_name(Context& ctx,
-                      struct args* args,
-                      struct args* preprocessor_args,
+                      const Args& args,
+                      Args& preprocessor_args,
                       struct hash* hash,
                       bool direct_mode)
 {
@@ -1710,9 +1709,9 @@ calculate_result_name(Context& ctx,
                  || ctx.guessed_compiler == GuessedCompiler::unknown;
 
   // First the arguments.
-  for (int i = 1; i < args->argc; i++) {
+  for (size_t i = 1; i < args.size(); i++) {
     // -L doesn't affect compilation (except for clang).
-    if (i < args->argc - 1 && str_eq(args->argv[i], "-L") && !is_clang) {
+    if (i < args.size() - 1 && str_eq(args->argv[i], "-L") && !is_clang) {
       i++;
       continue;
     }
@@ -1791,7 +1790,7 @@ calculate_result_name(Context& ctx,
       }
     }
 
-    char* p = nullptr;
+    const char* p = nullptr;
     if (str_startswith(args->argv[i], "-specs=")) {
       p = args->argv[i] + 7;
     } else if (str_startswith(args->argv[i], "--specs=")) {
@@ -1818,7 +1817,7 @@ calculate_result_name(Context& ctx,
       }
     }
 
-    if (str_eq(args->argv[i], "-Xclang") && i + 3 < args->argc
+    if (str_eq(args->argv[i], "-Xclang") && i + 3 < args.size()
         && str_eq(args->argv[i + 1], "-load")
         && str_eq(args->argv[i + 2], "-Xclang")) {
       auto st = Stat::stat(args->argv[i + 3], Stat::OnError::log);
@@ -1832,7 +1831,7 @@ calculate_result_name(Context& ctx,
 
     if ((str_eq(args->argv[i], "-ccbin")
          || str_eq(args->argv[i], "--compiler-bindir"))
-        && i + 1 < args->argc) {
+        && i + 1 < args.size()) {
       auto st = Stat::stat(args->argv[i + 1], Stat::OnError::log);
       if (st) {
         found_ccbin = true;
@@ -1846,7 +1845,7 @@ calculate_result_name(Context& ctx,
     // All other arguments are included in the hash.
     hash_delimiter(hash, "arg");
     hash_string(hash, args->argv[i]);
-    if (i + 1 < args->argc && compopt_takes_arg(args->argv[i])) {
+    if (i + 1 < args.size() && compopt_takes_arg(args->argv[i])) {
       i++;
       hash_delimiter(hash, "arg");
       hash_string(hash, args->argv[i]);
@@ -1961,7 +1960,6 @@ calculate_result_name(Context& ctx,
       cc_log("Did not find result name in manifest");
     }
   } else {
-    assert(preprocessor_args);
     if (ctx.args_info.arch_args_size == 0) {
       result_name = get_result_name_from_cpp(ctx, preprocessor_args, hash);
       cc_log("Got result name from preprocessor");
@@ -2073,7 +2071,7 @@ from_cache(Context& ctx,
 // Find the real compiler. We just search the PATH to find an executable of the
 // same name that isn't a link to ourselves.
 static void
-find_compiler(const Context& ctx, const char* const* argv)
+find_compiler(Context& ctx, const char* const* argv)
 {
   // We might be being invoked like "ccache gcc -c foo.c".
   std::string base(Util::base_name(argv[0]));
@@ -2099,7 +2097,7 @@ find_compiler(const Context& ctx, const char* const* argv)
     fatal("Recursive invocation (the name of the ccache binary must be \"%s\")",
           MYNAME);
   }
-  ctx.orig_args->argv[0] = compiler;
+  ctx.orig_args[0] = compiler;
 }
 
 bool
@@ -2178,10 +2176,10 @@ detect_pch(Context& ctx, const char* option, const char* arg, bool* found_pch)
 // incremented.
 optional<enum stats>
 process_args(Context& ctx,
-             struct args* args,
-             struct args** preprocessor_args,
-             struct args** extra_args_to_hash,
-             struct args** compiler_args)
+             const Args& args,
+             Args& preprocessor_args,
+             Args& extra_args_to_hash,
+             Args& compiler_args)
 {
   ArgsInfo& args_info = ctx.args_info;
   Config& config = ctx.config;
@@ -2211,32 +2209,27 @@ process_args(Context& ctx,
   // expanded_args is a copy of the original arguments given to the compiler
   // but with arguments from @file and similar constructs expanded. It's only
   // used as a temporary data structure to loop over.
-  struct args* expanded_args = args_copy(args);
-  ArgsScopeGuard expanded_args_guard(expanded_args);
+  Args expanded_args = args;
 
   // common_args contains all original arguments except:
   // * those that never should be passed to the preprocessor,
   // * those that only should be passed to the preprocessor (if run_second_cpp
   //   is false), and
   // * dependency options (like -MD and friends).
-  struct args* common_args = args_init(0, nullptr);
-  ArgsScopeGuard common_args_guard(common_args);
+  Args common_args;
 
   // cpp_args contains arguments that were not added to common_args, i.e. those
   // that should only be passed to the preprocessor if run_second_cpp is false.
   // If run_second_cpp is true, they will be passed to the compiler as well.
-  struct args* cpp_args = args_init(0, nullptr);
-  ArgsScopeGuard cpp_args_guard(cpp_args);
+  Args cpp_args;
 
   // dep_args contains dependency options like -MD. They are only passed to the
   // preprocessor, never to the compiler.
-  struct args* dep_args = args_init(0, nullptr);
-  ArgsScopeGuard dep_args_guard(dep_args);
+  Args dep_args;
 
   // compiler_only_args contains arguments that should only be passed to the
   // compiler, not the preprocessor.
-  struct args* compiler_only_args =
-    args_init(0, nullptr); // will leak on failure
+  Args compiler_only_args;
 
   bool found_color_diagnostics = false;
   bool found_directives_only = false;
@@ -2246,18 +2239,16 @@ process_args(Context& ctx,
   // Collect extra arguments that should be added.
   auto add_extra_arg = [&args_info, &config](const char* arg) {
     if (config.depend_mode()) {
-      if (args_info.depend_extra_args == nullptr) {
-        args_info.depend_extra_args = args_init(0, nullptr);
-      }
       args_add(args_info.depend_extra_args, arg);
     }
   };
 
-  int argc = expanded_args->argc;
-  char** argv = expanded_args->argv;
+  auto& argv = expanded_args->argv;
   args_add(common_args, argv[0]);
 
-  for (int i = 1; i < argc; i++) {
+  for (size_t i = 1; i < expanded_args.size(); i++) {
+    size_t argc = expanded_args.size();
+
     // The user knows best: just swallow the next arg.
     if (str_eq(argv[i], "--ccache-skip")) {
       i++;
@@ -2276,20 +2267,18 @@ process_args(Context& ctx,
 
     // Handle "@file" argument.
     if (str_startswith(argv[i], "@") || str_startswith(argv[i], "-@")) {
-      char* argpath = argv[i] + 1;
+      const char* argpath = argv[i] + 1;
 
       if (argpath[-1] == '-') {
         ++argpath;
       }
-      struct args* file_args = args_init_from_gcc_atfile(argpath);
+      auto file_args = args_init_from_gcc_atfile(argpath);
       if (!file_args) {
         cc_log("Couldn't read arg file %s", argpath);
         return STATS_ARGS;
       }
 
-      args_insert(expanded_args, i, file_args, true);
-      argc = expanded_args->argc;
-      argv = expanded_args->argv;
+      args_insert(expanded_args, i, *file_args, true);
       i--;
       continue;
     }
@@ -2304,31 +2293,29 @@ process_args(Context& ctx,
       ++i;
 
       // Argument is a comma-separated list of files.
-      char* str_start = argv[i];
-      char* str_end = strchr(str_start, ',');
-      int index = i + 1;
+      const char* str_start = argv[i];
+      const char* str_end = strchr(str_start, ',');
+      size_t index = i + 1;
 
       if (!str_end) {
         str_end = str_start + strlen(str_start);
       }
 
       while (str_end) {
-        *str_end = '\0';
-        struct args* file_args = args_init_from_gcc_atfile(str_start);
+        std::string path(str_start, str_end - str_start);
+        auto file_args = args_init_from_gcc_atfile(path);
         if (!file_args) {
-          cc_log("Couldn't read cuda options file %s", str_start);
+          cc_log("Couldn't read cuda options file %s", path.c_str());
           return STATS_ARGS;
         }
 
-        int new_index = file_args->argc + index;
-        args_insert(expanded_args, index, file_args, false);
+        size_t new_index = file_args->size() + index;
+        args_insert(expanded_args, index, *file_args, false);
         index = new_index;
         str_start = str_end;
         str_end = strchr(str_start, ',');
       }
 
-      argc = expanded_args->argc;
-      argv = expanded_args->argv;
       continue;
     }
 
@@ -2526,7 +2513,7 @@ process_args(Context& ctx,
     if (str_startswith(argv[i], "-MF")) {
       dependency_filename_specified = true;
 
-      char* arg;
+      const char* arg;
       bool separate_argument = (strlen(argv[i]) == 3);
       if (separate_argument) {
         // -MF arg
@@ -2834,7 +2821,7 @@ process_args(Context& ctx,
     // Same as above but options with concatenated argument beginning with a
     // slash.
     if (argv[i][0] == '-') {
-      char* slash_pos = strchr(argv[i], '/');
+      const char* slash_pos = strchr(argv[i], '/');
       if (slash_pos) {
         char* option = x_strndup(argv[i], slash_pos - argv[i]);
         if (compopt_takes_concat_arg(option) && compopt_takes_path(option)) {
@@ -3502,19 +3489,21 @@ cache_compilation(int argc, const char* const* argv)
     }
     // Else: Fall back to running the real compiler.
 
-    assert(ctx.orig_args);
+    assert(ctx.orig_args.size() > 0);
 
     args_strip(ctx.orig_args, "--ccache-");
     add_prefix(ctx, ctx.orig_args, ctx.config.prefix_command().c_str());
 
     cc_log("Failed; falling back to running the real compiler");
-    cc_log_argv("Executing ", ctx.orig_args->argv);
-    struct args* orig_args_for_execv = ctx.orig_args;
-    ctx.orig_args = nullptr; // Take over ownership.
+
+    // exitfn_call deletes ctx and thereby ctx.orig_orgs, so save it.
+    Args saved_orig_args(std::move(ctx.orig_args));
+    auto execv_argv = saved_orig_args.to_argv();
+
+    cc_log_argv("Executing ", execv_argv.data());
     exitfn_call();
-    execv(orig_args_for_execv->argv[0], orig_args_for_execv->argv);
-    fatal(
-      "execv of %s failed: %s", orig_args_for_execv->argv[0], strerror(errno));
+    execv(execv_argv[0], const_cast<char* const*>(execv_argv.data()));
+    fatal("execv of %s failed: %s", execv_argv[0], strerror(errno));
   }
 }
 
@@ -3562,19 +3551,16 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   MTR_END("main", "guess_compiler");
 
   // Arguments (except -E) to send to the preprocessor.
-  struct args* preprocessor_args;
+  Args preprocessor_args;
   // Arguments not sent to the preprocessor but that should be part of the
   // hash.
-  struct args* extra_args_to_hash;
+  Args extra_args_to_hash;
   // Arguments to send to the real compiler.
-  struct args* compiler_args;
+  Args compiler_args;
   MTR_BEGIN("main", "process_args");
 
-  auto error = process_args(ctx,
-                            ctx.orig_args,
-                            &preprocessor_args,
-                            &extra_args_to_hash,
-                            &compiler_args);
+  auto error = process_args(
+    ctx, ctx.orig_args, preprocessor_args, extra_args_to_hash, compiler_args);
   if (error) {
     failed(*error);
   }
@@ -3645,7 +3631,7 @@ do_cache_compilation(Context& ctx, const char* const* argv)
                   "DIRECT MODE",
                   debug_text_file);
 
-  struct args* args_to_hash = args_copy(preprocessor_args);
+  Args args_to_hash = preprocessor_args;
   args_extend(args_to_hash, extra_args_to_hash);
 
   bool put_result_in_manifest = false;
@@ -3654,8 +3640,9 @@ do_cache_compilation(Context& ctx, const char* const* argv)
   if (ctx.config.direct_mode()) {
     cc_log("Trying direct lookup");
     MTR_BEGIN("hash", "direct_hash");
+    Args dummy_args;
     result_name =
-      calculate_result_name(ctx, args_to_hash, nullptr, direct_hash, true);
+      calculate_result_name(ctx, args_to_hash, dummy_args, direct_hash, true);
     MTR_END("hash", "direct_hash");
     if (result_name) {
       update_cached_result_globals(ctx, result_name);
index 97c9f6e0ddd5bb55620bc7e03346f103ef4787cc..572961b1edcc0a668c7d044da322f3ca16765a5f 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "system.hpp"
 
+#include "Args.hpp"
 #include "Counters.hpp"
 #include "stats.hpp"
 
@@ -61,8 +62,8 @@ enum class GuessedCompiler { clang, gcc, nvcc, pump, unknown };
 void block_signals();
 void unblock_signals();
 nonstd::optional<enum stats> process_args(Context& ctx,
-                                          struct args* args,
-                                          struct args** preprocessor_args,
-                                          struct args** extra_args_to_hash,
-                                          struct args** compiler_args);
+                                          const Args& args,
+                                          Args& preprocessor_args,
+                                          Args& extra_args_to_hash,
+                                          Args& compiler_args);
 bool is_precompiled_header(const char* path);
index 81f545c6ee9425779498d7a8f6f477cfa64f8a7f..ee0db22bbdd44855d89ed62caa4188c870cb225e 100644 (file)
 
 #include "hashutil.hpp"
 
+#include "Args.hpp"
 #include "Config.hpp"
 #include "Context.hpp"
 #include "Stat.hpp"
-#include "args.hpp"
 #include "ccache.hpp"
 #include "execute.hpp"
 #include "logging.hpp"
@@ -313,13 +313,16 @@ hash_command_output(struct hash* hash,
   }
 #endif
 
-  struct args* args = args_init_from_string(command);
-  for (int i = 0; i < args->argc; i++) {
+  Args args = Args::from_string(command);
+
+  for (size_t i = 0; i < args.size(); i++) {
     if (str_eq(args->argv[i], "%compiler%")) {
       args_set(args, i, compiler);
     }
   }
-  cc_log_argv("Executing compiler check command ", args->argv);
+
+  auto argv = args.to_argv();
+  cc_log_argv("Executing compiler check command ", argv.data());
 
 #ifdef _WIN32
   PROCESS_INFORMATION pi;
@@ -327,9 +330,10 @@ hash_command_output(struct hash* hash,
   STARTUPINFO si;
   memset(&si, 0x00, sizeof(si));
 
-  char* path = find_executable_in_path(args->argv[0], nullptr, getenv("PATH"));
+  char* path =
+    find_executable_in_path(args[0].c_str(), nullptr, getenv("PATH"));
   if (!path) {
-    path = args->argv[0];
+    path = x_strdup(args[0].c_str());
   }
   char* sh = win32getshell(path);
   if (sh) {
@@ -350,14 +354,14 @@ hash_command_output(struct hash* hash,
   char* win32args;
   if (!cmd) {
     int length;
-    win32args = win32argvtos(sh, args->argv, &length);
+    win32args = win32argvtos(sh, argv.data(), &length);
   } else {
     win32args = (char*)command; // quoted
   }
   BOOL ret =
     CreateProcess(path, win32args, NULL, NULL, 1, 0, NULL, NULL, &si, &pi);
+  free(path);
   CloseHandle(pipe_out[1]);
-  args_free(args);
   free(win32args);
   if (!cmd) {
     free((char*)command); // Original argument was replaced above.
@@ -398,11 +402,10 @@ hash_command_output(struct hash* hash,
     close(0);
     dup2(pipefd[1], 1);
     dup2(pipefd[1], 2);
-    _exit(execvp(args->argv[0], args->argv));
+    _exit(execvp(argv[0], const_cast<char* const*>(argv.data())));
     // Never reached.
   } else {
     // Parent.
-    args_free(args);
     close(pipefd[1]);
     bool ok = hash_fd(hash, pipefd[0]);
     if (!ok) {
index 32bb3a05f2cd502f63acaee1bed54783419a4817..f15c9972509c6cde606b7e560c92b853ffe3bcd5 100644 (file)
@@ -18,9 +18,9 @@
 
 #include "framework.hpp"
 
+#include "../src/Args.hpp"
 #include "../src/Config.hpp"
 #include "../src/Util.hpp"
-#include "../src/args.hpp"
 #include "../src/ccache.hpp"
 #include "util.hpp"
 
@@ -287,32 +287,20 @@ bool
 cct_check_args_eq(const char* file,
                   int line,
                   const char* expression,
-                  const struct args* expected,
-                  const struct args* actual,
-                  bool free1,
-                  bool free2)
+                  const Args& expected,
+                  const Args& actual)
 {
-  bool result;
-
-  if (expected && actual && args_equal(actual, expected)) {
+  if (actual == expected) {
     cct_check_passed(file, line, expression);
-    result = true;
+    return true;
   } else {
-    char* exp_str = expected ? args_to_string(expected) : x_strdup("(null)");
-    char* act_str = actual ? args_to_string(actual) : x_strdup("(null)");
-    cct_check_failed(file, line, expression, exp_str, act_str);
-    free(exp_str);
-    free(act_str);
-    result = false;
-  }
-
-  if (free1) {
-    args_free(const_cast<struct args*>(expected));
-  }
-  if (free2) {
-    args_free(const_cast<struct args*>(actual));
+    cct_check_failed(file,
+                     line,
+                     expression,
+                     expected.to_string().c_str(),
+                     actual.to_string().c_str());
+    return false;
   }
-  return result;
 }
 
 void
index b3626d3210d09a07ec4b89492b45a6db54cc1ec2..3b94894bca98f31d2f4d2df16d96e76a667207bd 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2019 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -20,6 +20,8 @@
 
 #include "../src/system.hpp"
 
+class Args;
+
 // ============================================================================
 
 #define TEST_SUITE(name)                                                       \
 
 // ============================================================================
 
-#define CHECK_ARGS_EQ(expected, actual)                                        \
-  CHECK_POINTER_EQ_BASE(args, expected, actual, false, false)
-
-#define CHECK_ARGS_EQ_FREE1(expected, actual)                                  \
-  CHECK_POINTER_EQ_BASE(args, expected, actual, true, false)
-
-#define CHECK_ARGS_EQ_FREE2(expected, actual)                                  \
-  CHECK_POINTER_EQ_BASE(args, expected, actual, false, true)
-
-#define CHECK_ARGS_EQ_FREE12(expected, actual)                                 \
-  CHECK_POINTER_EQ_BASE(args, expected, actual, true, true)
+#define CHECK_ARGS_EQ_FREE12(e, a)                                             \
+  do {                                                                         \
+    if (!cct_check_args_eq(__FILE__, __LINE__, #a, (e), (a))) {                \
+      cct_test_end();                                                          \
+      cct_suite_end();                                                         \
+      return _test_counter;                                                    \
+    }                                                                          \
+  } while (false)
 
 // ============================================================================
 
@@ -179,10 +178,8 @@ bool cct_check_str_eq(const char* file,
 bool cct_check_args_eq(const char* file,
                        int line,
                        const char* expression,
-                       const struct args* expected,
-                       const struct args* actual,
-                       bool free1,
-                       bool free2);
+                       const Args& expected,
+                       const Args& actual);
 void cct_chdir(const char* path);
 void cct_wipe(const char* path);
 void cct_create_fresh_dir(const char* path);
diff --git a/unittest/test_Args.cpp b/unittest/test_Args.cpp
new file mode 100644 (file)
index 0000000..541cbc4
--- /dev/null
@@ -0,0 +1,217 @@
+// Copyright (C) 2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include "../src/Args.hpp"
+
+#include "third_party/catch.hpp"
+
+TEST_CASE("Args default constructor")
+{
+  Args args;
+  CHECK(args.size() == 0);
+}
+
+TEST_CASE("Args copy constructor")
+{
+  Args args1;
+  args1.push_back("foo");
+  args1.push_back("bar");
+
+  Args args2(args1);
+  CHECK(args1 == args2);
+}
+
+TEST_CASE("Args move constructor")
+{
+  Args args1;
+  args1.push_back("foo");
+  args1.push_back("bar");
+  const char* foo_pointer = args1[0].c_str();
+  const char* bar_pointer = args1[1].c_str();
+
+  Args args2(std::move(args1));
+  CHECK(args1.size() == 0);
+  CHECK(args2.size() == 2);
+  CHECK(args2[0].c_str() == foo_pointer);
+  CHECK(args2[1].c_str() == bar_pointer);
+}
+
+TEST_CASE("Args::from_argv")
+{
+  int argc = 2;
+  const char* argv[] = {"a", "b"};
+  Args args = Args::from_argv(argc, argv);
+  CHECK(args.size() == 2);
+  CHECK(args[0] == "a");
+  CHECK(args[1] == "b");
+}
+
+TEST_CASE("Args::from_string")
+{
+  Args args = Args::from_string(" c  d\te\r\nf ");
+  CHECK(args.size() == 4);
+  CHECK(args[0] == "c");
+  CHECK(args[1] == "d");
+  CHECK(args[2] == "e");
+  CHECK(args[3] == "f");
+}
+
+TEST_CASE("Args copy assignment operator")
+{
+  Args args1 = Args::from_string("x y");
+  Args args2;
+  args2 = args1;
+  CHECK(args2.size() == 2);
+  CHECK(args2[0] == "x");
+  CHECK(args2[1] == "y");
+}
+
+TEST_CASE("Args move assignment operator")
+{
+  Args args1 = Args::from_string("x y");
+  const char* x_pointer = args1[0].c_str();
+  const char* y_pointer = args1[1].c_str();
+
+  Args args2;
+  args2 = std::move(args1);
+  CHECK(args1.size() == 0);
+  CHECK(args2.size() == 2);
+  CHECK(args2[0].c_str() == x_pointer);
+  CHECK(args2[1] == y_pointer);
+}
+
+TEST_CASE("Args equality operators")
+{
+  Args args1 = Args::from_string("x y");
+  Args args2 = Args::from_string("x y");
+  Args args3 = Args::from_string("y x");
+  CHECK(args1 == args1);
+  CHECK(args1 == args2);
+  CHECK(args2 == args1);
+  CHECK(args1 != args3);
+  CHECK(args3 != args1);
+}
+
+TEST_CASE("Args::size")
+{
+  Args args;
+  CHECK(args.size() == 0);
+  args.push_back("1");
+  CHECK(args.size() == 1);
+  args.push_back("2");
+  CHECK(args.size() == 2);
+}
+
+TEST_CASE("Args indexing")
+{
+  const Args args1 = Args::from_string("1 2 3");
+  CHECK(args1[0] == "1");
+  CHECK(args1[1] == "2");
+  CHECK(args1[2] == "3");
+
+  Args args2 = Args::from_string("1 2 3");
+  CHECK(args2[0] == "1");
+  CHECK(args2[1] == "2");
+  CHECK(args2[2] == "3");
+}
+
+TEST_CASE("Args::to_argv")
+{
+  Args args = Args::from_string("1 2 3");
+  auto argv = args.to_argv();
+  CHECK(std::string(argv[0]) == "1");
+  CHECK(std::string(argv[1]) == "2");
+  CHECK(std::string(argv[2]) == "3");
+  CHECK(argv[3] == nullptr);
+}
+
+TEST_CASE("Args::to_string")
+{
+  CHECK(Args::from_string("a little string").to_string() == "a little string");
+}
+
+TEST_CASE("Args operations")
+{
+  Args args = Args::from_string("eeny meeny miny moe");
+  Args more_args = Args::from_string("x y");
+  Args no_args;
+
+  SECTION("erase_with_prefix")
+  {
+    args.erase_with_prefix("m");
+    CHECK(args == Args::from_string("eeny"));
+  }
+
+  SECTION("insert empty args")
+  {
+    args.insert(2, no_args);
+    CHECK(args == Args::from_string("eeny meeny miny moe"));
+  }
+
+  SECTION("insert non-empty args")
+  {
+    args.insert(4, more_args);
+    args.insert(2, more_args);
+    args.insert(0, more_args);
+    CHECK(args == Args::from_string("x y eeny meeny x y miny moe x y"));
+  }
+
+  SECTION("pop_back")
+  {
+    args.pop_back();
+    CHECK(args == Args::from_string("eeny meeny miny"));
+
+    args.pop_back(2);
+    CHECK(args == Args::from_string("eeny"));
+  }
+
+  SECTION("pop_front")
+  {
+    args.pop_front();
+    CHECK(args == Args::from_string("meeny miny moe"));
+
+    args.pop_front(2);
+    CHECK(args == Args::from_string("moe"));
+  }
+
+  SECTION("push_back string")
+  {
+    args.push_back("foo");
+    CHECK(args == Args::from_string("eeny meeny miny moe foo"));
+  }
+
+  SECTION("push_back args")
+  {
+    args.push_back(more_args);
+    CHECK(args == Args::from_string("eeny meeny miny moe x y"));
+  }
+
+  SECTION("push_front string")
+  {
+    args.push_front("foo");
+    CHECK(args == Args::from_string("foo eeny meeny miny moe"));
+  }
+
+  SECTION("replace")
+  {
+    args.replace(3, more_args);
+    args.replace(2, no_args);
+    args.replace(0, more_args);
+    CHECK(args == Args::from_string("x y meeny x y"));
+  }
+}
diff --git a/unittest/test_args.cpp b/unittest/test_args.cpp
deleted file mode 100644 (file)
index 0324a09..0000000
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// This program is free software; you can redistribute it and/or modify it
-// under the terms of the GNU General Public License as published by the Free
-// Software Foundation; either version 3 of the License, or (at your option)
-// any later version.
-//
-// This program is distributed in the hope that it will be useful, but WITHOUT
-// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-// more details.
-//
-// You should have received a copy of the GNU General Public License along with
-// this program; if not, write to the Free Software Foundation, Inc., 51
-// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-// This file contains tests for the functions operating on struct args.
-
-#include "../src/args.hpp"
-#include "framework.hpp"
-#include "util.hpp"
-
-TEST_SUITE(args)
-
-TEST(args_init_empty)
-{
-  struct args* args = args_init(0, nullptr);
-  CHECK(args);
-  CHECK_INT_EQ(0, args->argc);
-  CHECK(!args->argv[0]);
-  args_free(args);
-}
-
-TEST(args_init_populated)
-{
-  const char* argv[] = {"first", "second"};
-  struct args* args = args_init(2, argv);
-  CHECK(args);
-  CHECK_INT_EQ(2, args->argc);
-  CHECK_STR_EQ("first", args->argv[0]);
-  CHECK_STR_EQ("second", args->argv[1]);
-  CHECK(!args->argv[2]);
-  args_free(args);
-}
-
-TEST(args_init_from_string)
-{
-  struct args* args = args_init_from_string("first second\tthird\nfourth");
-  CHECK(args);
-  CHECK_INT_EQ(4, args->argc);
-  CHECK_STR_EQ("first", args->argv[0]);
-  CHECK_STR_EQ("second", args->argv[1]);
-  CHECK_STR_EQ("third", args->argv[2]);
-  CHECK_STR_EQ("fourth", args->argv[3]);
-  CHECK(!args->argv[4]);
-  args_free(args);
-}
-
-TEST(args_init_from_gcc_atfile)
-{
-  struct args* args;
-  const char* argtext =
-    "first\rsec\\\tond\tthi\\\\rd\nfourth  \tfif\\ th \"si'x\\\" th\""
-    " 'seve\nth'\\";
-
-  create_file("gcc_atfile", argtext);
-
-  args = args_init_from_gcc_atfile("gcc_atfile");
-  CHECK(args);
-  CHECK_INT_EQ(7, args->argc);
-  CHECK_STR_EQ("first", args->argv[0]);
-  CHECK_STR_EQ("sec\tond", args->argv[1]);
-  CHECK_STR_EQ("thi\\rd", args->argv[2]);
-  CHECK_STR_EQ("fourth", args->argv[3]);
-  CHECK_STR_EQ("fif th", args->argv[4]);
-  CHECK_STR_EQ("si'x\" th", args->argv[5]);
-#ifndef _WIN32
-  CHECK_STR_EQ("seve\nth", args->argv[6]);
-#else
-  CHECK_STR_EQ("seve\r\nth", args->argv[6]);
-#endif
-  CHECK(!args->argv[7]);
-  args_free(args);
-}
-
-TEST(args_copy)
-{
-  struct args* args1 = args_init_from_string("foo");
-  struct args* args2 = args_copy(args1);
-  CHECK_ARGS_EQ_FREE12(args1, args2);
-}
-
-TEST(args_add)
-{
-  struct args* args = args_init_from_string("first");
-  CHECK_INT_EQ(1, args->argc);
-  args_add(args, "second");
-  CHECK_INT_EQ(2, args->argc);
-  CHECK_STR_EQ("second", args->argv[1]);
-  CHECK(!args->argv[2]);
-  args_free(args);
-}
-
-TEST(args_extend)
-{
-  struct args* args1 = args_init_from_string("first");
-  struct args* args2 = args_init_from_string("second third");
-  CHECK_INT_EQ(1, args1->argc);
-  args_extend(args1, args2);
-  CHECK_INT_EQ(3, args1->argc);
-  CHECK_STR_EQ("second", args1->argv[1]);
-  CHECK_STR_EQ("third", args1->argv[2]);
-  CHECK(!args1->argv[3]);
-  args_free(args1);
-  args_free(args2);
-}
-
-TEST(args_pop)
-{
-  struct args* args = args_init_from_string("first second third");
-  args_pop(args, 2);
-  CHECK_INT_EQ(1, args->argc);
-  CHECK_STR_EQ("first", args->argv[0]);
-  CHECK(!args->argv[1]);
-  args_free(args);
-}
-
-TEST(args_set)
-{
-  struct args* args = args_init_from_string("first second third");
-  args_set(args, 1, "2nd");
-  CHECK_INT_EQ(3, args->argc);
-  CHECK_STR_EQ("first", args->argv[0]);
-  CHECK_STR_EQ("2nd", args->argv[1]);
-  CHECK_STR_EQ("third", args->argv[2]);
-  CHECK(!args->argv[3]);
-  args_free(args);
-}
-
-TEST(args_remove_first)
-{
-  struct args* args1 = args_init_from_string("first second third");
-  struct args* args2 = args_init_from_string("second third");
-  args_remove_first(args1);
-  CHECK_ARGS_EQ_FREE12(args1, args2);
-}
-
-TEST(args_add_prefix)
-{
-  struct args* args1 = args_init_from_string("second third");
-  struct args* args2 = args_init_from_string("first second third");
-  args_add_prefix(args1, "first");
-  CHECK_ARGS_EQ_FREE12(args1, args2);
-}
-
-TEST(args_strip)
-{
-  struct args* args1 = args_init_from_string("first xsecond third xfourth");
-  struct args* args2 = args_init_from_string("first third");
-  args_strip(args1, "x");
-  CHECK_ARGS_EQ_FREE12(args1, args2);
-}
-
-TEST(args_to_string)
-{
-  struct args* args = args_init_from_string("first second");
-  CHECK_STR_EQ_FREE2("first second", args_to_string(args));
-  args_free(args);
-}
-
-TEST(args_insert)
-{
-  struct args* args = args_init_from_string("first second third fourth fifth");
-
-  struct args* src1 = args_init_from_string("alpha beta gamma");
-  struct args* src2 = args_init_from_string("one");
-  struct args* src3 = args_init_from_string("");
-  struct args* src4 = args_init_from_string("alpha beta gamma");
-  struct args* src5 = args_init_from_string("one");
-  struct args* src6 = args_init_from_string("");
-
-  args_insert(args, 2, src1, true);
-  CHECK_STR_EQ_FREE2("first second alpha beta gamma fourth fifth",
-                     args_to_string(args));
-  CHECK_INT_EQ(7, args->argc);
-  args_insert(args, 2, src2, true);
-  CHECK_STR_EQ_FREE2("first second one beta gamma fourth fifth",
-                     args_to_string(args));
-  CHECK_INT_EQ(7, args->argc);
-  args_insert(args, 2, src3, true);
-  CHECK_STR_EQ_FREE2("first second beta gamma fourth fifth",
-                     args_to_string(args));
-  CHECK_INT_EQ(6, args->argc);
-
-  args_insert(args, 1, src4, false);
-  CHECK_STR_EQ_FREE2("first alpha beta gamma second beta gamma fourth fifth",
-                     args_to_string(args));
-  CHECK_INT_EQ(9, args->argc);
-  args_insert(args, 1, src5, false);
-  CHECK_STR_EQ_FREE2(
-    "first one alpha beta gamma second beta gamma fourth fifth",
-    args_to_string(args));
-  CHECK_INT_EQ(10, args->argc);
-  args_insert(args, 1, src6, false);
-  CHECK_STR_EQ_FREE2(
-    "first one alpha beta gamma second beta gamma fourth fifth",
-    args_to_string(args));
-  CHECK_INT_EQ(10, args->argc);
-
-  args_free(args);
-}
-
-TEST_SUITE_END
index 86d5ce12fd46483ff0060e3928a3e855dbc141b5..9a53c749461b81886a608e26f7c89f7f29a1d813 100644 (file)
 
 // This file contains tests for the processing of compiler arguments.
 
+#include "../src/Args.hpp"
 #include "../src/Config.hpp"
 #include "../src/Context.hpp"
 #include "../src/Util.hpp"
-#include "../src/args.hpp"
 #include "../src/ccache.hpp"
 #include "../src/stats.hpp"
 #include "framework.hpp"
@@ -71,28 +71,28 @@ TEST(dash_E_should_result_in_called_for_preprocessing)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string("cc -c foo.c -E");
-  struct args *preprocessed, *compiler;
+  Args orig = args_init_from_string("cc -c foo.c -E");
+  Args preprocessed;
+  Args extra;
+  Args compiler;
 
   create_file("foo.c", "");
-  CHECK(process_args(ctx, orig, &preprocessed, nullptr, &compiler)
+  CHECK(process_args(ctx, orig, preprocessed, extra, compiler)
         == STATS_PREPROCESSING);
-
-  args_free(orig);
 }
 
 TEST(dash_M_should_be_unsupported)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string("cc -c foo.c -M");
-  struct args *preprocessed, *compiler;
+  Args orig = args_init_from_string("cc -c foo.c -M");
+  Args preprocessed;
+  Args extra;
+  Args compiler;
 
   create_file("foo.c", "");
-  CHECK(process_args(ctx, orig, &preprocessed, nullptr, &compiler)
+  CHECK(process_args(ctx, orig, preprocessed, extra, compiler)
         == STATS_UNSUPPORTED_OPTION);
-
-  args_free(orig);
 }
 
 TEST(dependency_args_to_preprocessor_if_run_second_cpp_is_false)
@@ -102,24 +102,21 @@ TEST(dependency_args_to_preprocessor_if_run_second_cpp_is_false)
 #define DEP_ARGS                                                               \
   "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"        \
   " -Wp,-MMD,wpmmd -Wp,-MP -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf"
-  struct args* orig =
-    args_init_from_string("cc " DEP_ARGS " -c foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc " DEP_ARGS);
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc = args_init_from_string("cc -c");
+  Args orig = args_init_from_string("cc " DEP_ARGS " -c foo.c -o foo.o");
+  Args exp_cpp = args_init_from_string("cc " DEP_ARGS);
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("cc -c");
 #undef DEP_ARGS
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
   create_file("foo.c", "");
 
   ctx.config.set_run_second_cpp(false);
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(dependency_args_to_compiler_if_run_second_cpp_is_true)
@@ -129,23 +126,20 @@ TEST(dependency_args_to_compiler_if_run_second_cpp_is_true)
 #define DEP_ARGS                                                               \
   "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"        \
   " -Wp,-MMD,wpmmd -Wp,-MP -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf"
-  struct args* orig =
-    args_init_from_string("cc " DEP_ARGS " -c foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra = args_init_from_string(DEP_ARGS);
-  struct args* exp_cc = args_init_from_string("cc -c " DEP_ARGS);
+  Args orig = args_init_from_string("cc " DEP_ARGS " -c foo.c -o foo.o");
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string(DEP_ARGS);
+  Args exp_cc = args_init_from_string("cc -c " DEP_ARGS);
 #undef DEP_ARGS
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(cpp_only_args_to_preprocessor_if_run_second_cpp_is_false)
@@ -160,25 +154,23 @@ TEST(cpp_only_args_to_preprocessor_if_run_second_cpp_is_false)
 #define DEP_ARGS                                                               \
   "-MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"        \
   " -Wp,-MMD,wpmmd -Wp,-MP -Wp,-MT,wpmt -Wp,-MQ,wpmq -Wp,-MF,wpf"
-  struct args* orig =
+  Args orig =
     args_init_from_string("cc " CPP_ARGS " " DEP_ARGS " -c foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc " CPP_ARGS " " DEP_ARGS);
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc = args_init_from_string("cc -c");
+  Args exp_cpp = args_init_from_string("cc " CPP_ARGS " " DEP_ARGS);
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("cc -c");
 #undef DEP_ARGS
 #undef CPP_ARGS
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
   create_file("foo.c", "");
 
   ctx.config.set_run_second_cpp(false);
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(cpp_only_args_to_preprocessor_and_compiler_if_run_second_cpp_is_true)
@@ -193,24 +185,22 @@ TEST(cpp_only_args_to_preprocessor_and_compiler_if_run_second_cpp_is_true)
 #define DEP_ARGS                                                               \
   " -MD -MMD -MP -MF foo.d -MT mt1 -MT mt2 -MQ mq1 -MQ mq2 -Wp,-MD,wpmd"       \
   " -Wp,-MMD,wpmmd"
-  struct args* orig =
+  Args orig =
     args_init_from_string("cc " CPP_ARGS " " DEP_ARGS " -c foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc " CPP_ARGS);
-  struct args* exp_extra = args_init_from_string(DEP_ARGS);
-  struct args* exp_cc = args_init_from_string("cc " CPP_ARGS " -c " DEP_ARGS);
+  Args exp_cpp = args_init_from_string("cc " CPP_ARGS);
+  Args exp_extra = args_init_from_string(DEP_ARGS);
+  Args exp_cc = args_init_from_string("cc " CPP_ARGS " -c " DEP_ARGS);
 #undef DEP_ARGS
 #undef CPP_ARGS
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(dependency_args_that_take_an_argument_should_not_require_space_delimiter)
@@ -218,135 +208,120 @@ TEST(dependency_args_that_take_an_argument_should_not_require_space_delimiter)
   Context ctx;
 
 #define DEP_ARGS "-MMD -MFfoo.d -MT mt -MTmt -MQmq"
-  struct args* orig =
-    args_init_from_string("cc -c " DEP_ARGS " foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra = args_init_from_string(DEP_ARGS);
-  struct args* exp_cc = args_init_from_string("cc -c " DEP_ARGS);
+  Args orig = args_init_from_string("cc -c " DEP_ARGS " foo.c -o foo.o");
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string(DEP_ARGS);
+  Args exp_cc = args_init_from_string("cc -c " DEP_ARGS);
 #undef DEP_ARGS
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MQ_flag_should_not_be_added_if_run_second_cpp_is_true)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("cc -c -MD foo.c -MF foo.d -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra = args_init_from_string("-MD -MF foo.d");
-  struct args* exp_cc = args_init_from_string("cc -c -MD -MF foo.d");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("cc -c -MD foo.c -MF foo.d -o foo.o");
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string("-MD -MF foo.d");
+  Args exp_cc = args_init_from_string("cc -c -MD -MF foo.d");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MQ_flag_should_be_added_if_run_second_cpp_is_false)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("cc -c -MD foo.c -MF foo.d -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc -MD -MF foo.d -MQ foo.o");
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc = args_init_from_string("cc -c");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("cc -c -MD foo.c -MF foo.d -o foo.o");
+  Args exp_cpp = args_init_from_string("cc -MD -MF foo.d -MQ foo.o");
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("cc -c");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
   create_file("foo.c", "");
 
   ctx.config.set_run_second_cpp(false);
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MF_should_be_added_if_run_second_cpp_is_false)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string("cc -c -MD foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc -MD -MF foo.d -MQ foo.o");
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc = args_init_from_string("cc -c");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("cc -c -MD foo.c -o foo.o");
+  Args exp_cpp = args_init_from_string("cc -MD -MF foo.d -MQ foo.o");
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("cc -c");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
   ctx.config.set_run_second_cpp(false);
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MF_should_not_be_added_if_run_second_cpp_is_true)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string("cc -c -MD foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra = args_init_from_string("-MD");
-  struct args* exp_cc = args_init_from_string("cc -c -MD");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("cc -c -MD foo.c -o foo.o");
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string("-MD");
+  Args exp_cc = args_init_from_string("cc -c -MD");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(equal_sign_after_MF_should_be_removed)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string("cc -c -MF=path foo.c -o foo.o");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra = args_init_from_string("-MFpath");
-  struct args* exp_cc = args_init_from_string("cc -c -MFpath");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("cc -c -MF=path foo.c -o foo.o");
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string("-MFpath");
+  Args exp_cc = args_init_from_string("cc -c -MFpath");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(sysroot_should_be_rewritten_if_basedir_is_used)
@@ -354,10 +329,10 @@ TEST(sysroot_should_be_rewritten_if_basedir_is_used)
   Context ctx;
 
   char* arg_string;
-  struct args* orig;
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
   ctx.config.set_base_dir(get_root());
@@ -366,12 +341,8 @@ TEST(sysroot_should_be_rewritten_if_basedir_is_used)
   orig = args_init_from_string(arg_string);
   free(arg_string);
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_STR_EQ(act_cpp->argv[1], "--sysroot=./foo/bar");
-
-  args_free(orig);
-  args_free(act_cpp);
-  args_free(act_cc);
 }
 
 TEST(sysroot_with_separate_argument_should_be_rewritten_if_basedir_is_used)
@@ -379,10 +350,10 @@ TEST(sysroot_with_separate_argument_should_be_rewritten_if_basedir_is_used)
   Context ctx;
 
   char* arg_string;
-  struct args* orig;
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
   ctx.config.set_base_dir(get_root());
@@ -390,196 +361,165 @@ TEST(sysroot_with_separate_argument_should_be_rewritten_if_basedir_is_used)
   orig = args_init_from_string(arg_string);
   free(arg_string);
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_STR_EQ(act_cpp->argv[1], "--sysroot");
   CHECK_STR_EQ(act_cpp->argv[2], "./foo");
-
-  args_free(orig);
-  args_free(act_cpp);
-  args_free(act_cc);
 }
 
 TEST(MF_flag_with_immediate_argument_should_work_as_last_argument)
 {
   Context ctx;
 
-  struct args* orig =
+  Args orig =
     args_init_from_string("cc -c foo.c -o foo.o -MMD -MT bar -MFfoo.d");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra = args_init_from_string("-MMD -MT bar -MFfoo.d");
-  struct args* exp_cc = args_init_from_string("cc -c -MMD -MT bar -MFfoo.d");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string("-MMD -MT bar -MFfoo.d");
+  Args exp_cc = args_init_from_string("cc -c -MMD -MT bar -MFfoo.d");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MT_flag_with_immediate_argument_should_work_as_last_argument)
 {
   Context ctx;
 
-  struct args* orig =
+  Args orig =
     args_init_from_string("cc -c foo.c -o foo.o -MMD -MFfoo.d -MT foo -MTbar");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra =
-    args_init_from_string("-MMD -MFfoo.d -MT foo -MTbar");
-  struct args* exp_cc =
-    args_init_from_string("cc -c -MMD -MFfoo.d -MT foo -MTbar");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string("-MMD -MFfoo.d -MT foo -MTbar");
+  Args exp_cc = args_init_from_string("cc -c -MMD -MFfoo.d -MT foo -MTbar");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MQ_flag_with_immediate_argument_should_work_as_last_argument)
 {
   Context ctx;
 
-  struct args* orig =
+  Args orig =
     args_init_from_string("cc -c foo.c -o foo.o -MMD -MFfoo.d -MQ foo -MQbar");
-  struct args* exp_cpp = args_init_from_string("cc");
-  struct args* exp_extra =
-    args_init_from_string("-MMD -MFfoo.d -MQ foo -MQbar");
-  struct args* exp_cc =
-    args_init_from_string("cc -c -MMD -MFfoo.d -MQ foo -MQbar");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args exp_cpp = args_init_from_string("cc");
+  Args exp_extra = args_init_from_string("-MMD -MFfoo.d -MQ foo -MQbar");
+  Args exp_cc = args_init_from_string("cc -c -MMD -MFfoo.d -MQ foo -MQbar");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MQ_flag_without_immediate_argument_should_not_add_MQobj)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQ foo.d foo.c");
-  struct args* exp_cpp = args_init_from_string("gcc");
-  struct args* exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MQ foo.d");
-  struct args* exp_cc =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQ foo.d");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQ foo.d foo.c");
+  Args exp_cpp = args_init_from_string("gcc");
+  Args exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MQ foo.d");
+  Args exp_cc = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQ foo.d");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MT_flag_without_immediate_argument_should_not_add_MTobj)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MT foo.d foo.c");
-  struct args* exp_cpp = args_init_from_string("gcc");
-  struct args* exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MT foo.d");
-  struct args* exp_cc =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MT foo.d");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MT foo.d foo.c");
+  Args exp_cpp = args_init_from_string("gcc");
+  Args exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MT foo.d");
+  Args exp_cc = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MT foo.d");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MQ_flag_with_immediate_argument_should_not_add_MQobj)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQfoo.d foo.c");
-  struct args* exp_cpp = args_init_from_string("gcc");
-  struct args* exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MQfoo.d");
-  struct args* exp_cc =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQfoo.d");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQfoo.d foo.c");
+  Args exp_cpp = args_init_from_string("gcc");
+  Args exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MQfoo.d");
+  Args exp_cc = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MQfoo.d");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(MT_flag_with_immediate_argument_should_not_add_MQobj)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MTfoo.d foo.c");
-  struct args* exp_cpp = args_init_from_string("gcc");
-  struct args* exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MTfoo.d");
-  struct args* exp_cc =
-    args_init_from_string("gcc -c -MD -MP -MFfoo.d -MTfoo.d");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MTfoo.d foo.c");
+  Args exp_cpp = args_init_from_string("gcc");
+  Args exp_extra = args_init_from_string("-MD -MP -MFfoo.d -MTfoo.d");
+  Args exp_cc = args_init_from_string("gcc -c -MD -MP -MFfoo.d -MTfoo.d");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(fprofile_flag_with_existing_dir_should_be_rewritten_to_real_path)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("gcc -c -fprofile-generate=some/dir foo.c");
-  struct args* exp_cpp = args_init_from_string("gcc");
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc = args_init_from_string("gcc");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("gcc -c -fprofile-generate=some/dir foo.c");
+  Args exp_cpp = args_init_from_string("gcc");
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("gcc");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   char* s;
 
@@ -593,37 +533,30 @@ TEST(fprofile_flag_with_existing_dir_should_be_rewritten_to_real_path)
   args_add(exp_cc, "-c");
   free(s);
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(fprofile_flag_with_nonexistent_dir_should_not_be_rewritten)
 {
   Context ctx;
 
-  struct args* orig =
-    args_init_from_string("gcc -c -fprofile-generate=some/dir foo.c");
-  struct args* exp_cpp =
-    args_init_from_string("gcc -fprofile-generate=some/dir");
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc =
-    args_init_from_string("gcc -fprofile-generate=some/dir -c");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("gcc -c -fprofile-generate=some/dir foo.c");
+  Args exp_cpp = args_init_from_string("gcc -fprofile-generate=some/dir");
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("gcc -fprofile-generate=some/dir -c");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(isystem_flag_with_separate_arg_should_be_rewritten_if_basedir_is_used)
@@ -631,10 +564,10 @@ TEST(isystem_flag_with_separate_arg_should_be_rewritten_if_basedir_is_used)
   Context ctx;
 
   char* arg_string;
-  struct args* orig;
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
   ctx.config.set_base_dir(get_root());
@@ -642,12 +575,8 @@ TEST(isystem_flag_with_separate_arg_should_be_rewritten_if_basedir_is_used)
   orig = args_init_from_string(arg_string);
   free(arg_string);
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_STR_EQ("./foo", act_cpp->argv[2]);
-
-  args_free(orig);
-  args_free(act_cpp);
-  args_free(act_cc);
 }
 
 TEST(isystem_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
@@ -656,10 +585,10 @@ TEST(isystem_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
 
   char* cwd;
   char* arg_string;
-  struct args* orig;
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
   ctx.config.set_base_dir("/"); // posix
@@ -669,13 +598,10 @@ TEST(isystem_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
   orig = args_init_from_string(arg_string);
   free(arg_string);
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_STR_EQ("-isystem./foo", act_cpp->argv[1]);
 
   free(cwd);
-  args_free(orig);
-  args_free(act_cpp);
-  args_free(act_cc);
 }
 
 TEST(I_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
@@ -684,10 +610,10 @@ TEST(I_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
 
   char* cwd;
   char* arg_string;
-  struct args* orig;
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
   ctx.config.set_base_dir(x_strdup("/")); // posix
@@ -697,79 +623,70 @@ TEST(I_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
   orig = args_init_from_string(arg_string);
   free(arg_string);
 
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_STR_EQ("-I./foo", act_cpp->argv[1]);
 
   free(cwd);
-  args_free(orig);
-  args_free(act_cpp);
-  args_free(act_cc);
 }
 
 TEST(debug_flag_order_with_known_option_first)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string("cc -g1 -gsplit-dwarf foo.c -c");
-  struct args* exp_cpp = args_init_from_string("cc -g1 -gsplit-dwarf");
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc = args_init_from_string("cc -g1 -gsplit-dwarf -c");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("cc -g1 -gsplit-dwarf foo.c -c");
+  Args exp_cpp = args_init_from_string("cc -g1 -gsplit-dwarf");
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("cc -g1 -gsplit-dwarf -c");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(debug_flag_order_with_known_option_last)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string("cc -gsplit-dwarf -g1 foo.c -c");
-  struct args* exp_cpp = args_init_from_string("cc -gsplit-dwarf -g1");
-  struct args* exp_extra = args_init(0, nullptr);
-  struct args* exp_cc = args_init_from_string("cc -gsplit-dwarf -g1 -c");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args orig = args_init_from_string("cc -gsplit-dwarf -g1 foo.c -c");
+  Args exp_cpp = args_init_from_string("cc -gsplit-dwarf -g1");
+  Args exp_extra = args_init(0, NULL);
+  Args exp_cc = args_init_from_string("cc -gsplit-dwarf -g1 -c");
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST(options_not_to_be_passed_to_the_preprocesor)
 {
   Context ctx;
 
-  struct args* orig = args_init_from_string(
+  Args orig = args_init_from_string(
     "cc -Wa,foo foo.c -g -c -DX -Werror -Xlinker fie -Xlinker,fum -Wno-error");
-  struct args* exp_cpp = args_init_from_string("cc -g -DX");
-  struct args* exp_extra = args_init_from_string(
+  Args exp_cpp = args_init_from_string("cc -g -DX");
+  Args exp_extra = args_init_from_string(
     " -Wa,foo -Werror -Xlinker fie -Xlinker,fum -Wno-error");
-  struct args* exp_cc = args_init_from_string(
+  Args exp_cc = args_init_from_string(
     "cc -g -Wa,foo -Werror -Xlinker fie -Xlinker,fum -Wno-error -DX -c");
-  struct args* act_cpp = nullptr;
-  struct args* act_extra = nullptr;
-  struct args* act_cc = nullptr;
+  Args act_cpp;
+  Args act_extra;
+  Args act_cc;
 
   create_file("foo.c", "");
-  CHECK(!process_args(ctx, orig, &act_cpp, &act_extra, &act_cc));
+  CHECK(!process_args(ctx, orig, act_cpp, act_extra, act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_extra, act_extra);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
-
-  args_free(orig);
 }
 
 TEST_SUITE_END
diff --git a/unittest/test_legacy_args.cpp b/unittest/test_legacy_args.cpp
new file mode 100644 (file)
index 0000000..20102e7
--- /dev/null
@@ -0,0 +1,192 @@
+// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+// This file contains tests for the functions operating on struct args.
+
+#include "../src/Args.hpp"
+#include "framework.hpp"
+#include "util.hpp"
+
+TEST_SUITE(args)
+
+TEST(args_init_empty)
+{
+  Args args;
+  CHECK_INT_EQ(0, args.size());
+  CHECK(!args->argv[0]);
+}
+
+TEST(args_init_populated)
+{
+  const char* argv[] = {"first", "second", nullptr};
+  Args args = Args::from_argv(2, argv);
+  CHECK_INT_EQ(2, args.size());
+  CHECK_STR_EQ("first", args->argv[0]);
+  CHECK_STR_EQ("second", args->argv[1]);
+  CHECK(!args->argv[2]);
+}
+
+TEST(args_init_from_string)
+{
+  Args args = args_init_from_string("first second\tthird\nfourth");
+  CHECK_INT_EQ(4, args.size());
+  CHECK_STR_EQ("first", args->argv[0]);
+  CHECK_STR_EQ("second", args->argv[1]);
+  CHECK_STR_EQ("third", args->argv[2]);
+  CHECK_STR_EQ("fourth", args->argv[3]);
+  CHECK(!args->argv[4]);
+}
+
+TEST(args_init_from_gcc_atfile)
+{
+  const char* argtext =
+    "first\rsec\\\tond\tthi\\\\rd\nfourth  \tfif\\ th \"si'x\\\" th\""
+    " 'seve\nth'\\";
+
+  create_file("gcc_atfile", argtext);
+
+  Args args = *args_init_from_gcc_atfile("gcc_atfile");
+  CHECK_INT_EQ(7, args->size());
+  CHECK_STR_EQ("first", args->argv[0]);
+  CHECK_STR_EQ("sec\tond", args->argv[1]);
+  CHECK_STR_EQ("thi\\rd", args->argv[2]);
+  CHECK_STR_EQ("fourth", args->argv[3]);
+  CHECK_STR_EQ("fif th", args->argv[4]);
+  CHECK_STR_EQ("si'x\" th", args->argv[5]);
+  CHECK_STR_EQ("seve\nth", args->argv[6]);
+  CHECK(!args->argv[7]);
+}
+
+TEST(args_copy)
+{
+  Args args1 = args_init_from_string("foo");
+  Args args2 = args1;
+  CHECK_ARGS_EQ_FREE12(args1, args2);
+}
+
+TEST(args_add)
+{
+  Args args = args_init_from_string("first");
+  CHECK_INT_EQ(1, args.size());
+  args_add(args, "second");
+  CHECK_INT_EQ(2, args.size());
+  CHECK_STR_EQ("second", args->argv[1]);
+  CHECK(!args->argv[2]);
+}
+
+TEST(args_extend)
+{
+  Args args1 = args_init_from_string("first");
+  Args args2 = args_init_from_string("second third");
+  CHECK_INT_EQ(1, args1.size());
+  args_extend(args1, args2);
+  CHECK_INT_EQ(3, args1.size());
+  CHECK_STR_EQ("second", args1->argv[1]);
+  CHECK_STR_EQ("third", args1->argv[2]);
+  CHECK(!args1->argv[3]);
+}
+
+TEST(args_pop)
+{
+  Args args = args_init_from_string("first second third");
+  args_pop(args, 2);
+  CHECK_INT_EQ(1, args.size());
+  CHECK_STR_EQ("first", args->argv[0]);
+  CHECK(!args->argv[1]);
+}
+
+TEST(args_set)
+{
+  Args args = args_init_from_string("first second third");
+  args_set(args, 1, "2nd");
+  CHECK_INT_EQ(3, args.size());
+  CHECK_STR_EQ("first", args->argv[0]);
+  CHECK_STR_EQ("2nd", args->argv[1]);
+  CHECK_STR_EQ("third", args->argv[2]);
+  CHECK(!args->argv[3]);
+}
+
+TEST(args_remove_first)
+{
+  Args args1 = args_init_from_string("first second third");
+  Args args2 = args_init_from_string("second third");
+  args_remove_first(args1);
+  CHECK_ARGS_EQ_FREE12(args1, args2);
+}
+
+TEST(args_add_prefix)
+{
+  Args args1 = args_init_from_string("second third");
+  Args args2 = args_init_from_string("first second third");
+  args_add_prefix(args1, "first");
+  CHECK_ARGS_EQ_FREE12(args1, args2);
+}
+
+TEST(args_strip)
+{
+  Args args1 = args_init_from_string("first xsecond third xfourth");
+  Args args2 = args_init_from_string("first third");
+  args_strip(args1, "x");
+  CHECK_ARGS_EQ_FREE12(args1, args2);
+}
+
+TEST(args_to_string)
+{
+  Args args = args_init_from_string("first second");
+  CHECK_STR_EQ("first second", args.to_string().c_str());
+}
+
+TEST(args_insert)
+{
+  Args args = args_init_from_string("first second third fourth fifth");
+
+  Args src1 = args_init_from_string("alpha beta gamma");
+  Args src2 = args_init_from_string("one");
+  Args src3 = args_init_from_string("");
+  Args src4 = args_init_from_string("alpha beta gamma");
+  Args src5 = args_init_from_string("one");
+  Args src6 = args_init_from_string("");
+
+  args_insert(args, 2, src1, true);
+  CHECK_STR_EQ("first second alpha beta gamma fourth fifth",
+               args.to_string().c_str());
+  CHECK_INT_EQ(7, args.size());
+  args_insert(args, 2, src2, true);
+  CHECK_STR_EQ("first second one beta gamma fourth fifth",
+               args.to_string().c_str());
+  CHECK_INT_EQ(7, args.size());
+  args_insert(args, 2, src3, true);
+  CHECK_STR_EQ("first second beta gamma fourth fifth",
+               args.to_string().c_str());
+  CHECK_INT_EQ(6, args.size());
+
+  args_insert(args, 1, src4, false);
+  CHECK_STR_EQ("first alpha beta gamma second beta gamma fourth fifth",
+               args.to_string().c_str());
+  CHECK_INT_EQ(9, args.size());
+  args_insert(args, 1, src5, false);
+  CHECK_STR_EQ("first one alpha beta gamma second beta gamma fourth fifth",
+               args.to_string().c_str());
+  CHECK_INT_EQ(10, args.size());
+  args_insert(args, 1, src6, false);
+  CHECK_STR_EQ("first one alpha beta gamma second beta gamma fourth fifth",
+               args.to_string().c_str());
+  CHECK_INT_EQ(10, args.size());
+}
+
+TEST_SUITE_END