From 41291b20f3881cac781e5e628f8b892b29c7b39c Mon Sep 17 00:00:00 2001
From: Matthew Woehlke <matthew.woehlke@kitware.com>
Date: Wed, 28 Sep 2016 12:07:39 -0400
Subject: cmake_parse_arguments: Fix PARSE_ARGV multi-value argument handling

The `PARSE_ARGV` mode was recently added to help functions properly
parse their arguments even when those arguments may be quoted and
contain literal `;` in their values.  Fix the implementation to encode
`;`s in reported multi-value arguments and in `UNPARSED_ARGUMENTS` so
that `;`s in the individual values are preserved in the lists.  This
allows clients to access all their argument values correctly.
---
 Source/cmParseArgumentsCommand.cxx               | 25 ++++++++++++++++++++++--
 Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake | 22 +++++++++++++++++++--
 2 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx
index e8de5b6..55d71ea 100644
--- a/Source/cmParseArgumentsCommand.cxx
+++ b/Source/cmParseArgumentsCommand.cxx
@@ -4,6 +4,19 @@
 
 #include "cmAlgorithms.h"
 
+static std::string escape_arg(const std::string& arg)
+{
+  // replace ";" with "\;" so output argument lists will split correctly
+  std::string escapedArg;
+  for (size_t i = 0; i < arg.size(); ++i) {
+    if (arg[i] == ';') {
+      escapedArg += '\\';
+    }
+    escapedArg += arg[i];
+  }
+  return escapedArg;
+}
+
 bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
                                           cmExecutionStatus&)
 {
@@ -165,10 +178,18 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
         insideValues = NONE;
         break;
       case MULTI:
-        multi[currentArgName].push_back(*argIter);
+        if (parseFromArgV) {
+          multi[currentArgName].push_back(escape_arg(*argIter));
+        } else {
+          multi[currentArgName].push_back(*argIter);
+        }
         break;
       default:
-        unparsed.push_back(*argIter);
+        if (parseFromArgV) {
+          unparsed.push_back(escape_arg(*argIter));
+        } else {
+          unparsed.push_back(*argIter);
+        }
         break;
     }
   }
diff --git a/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake b/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake
index 61bde03..63a1b01 100644
--- a/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake
+++ b/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake
@@ -1,5 +1,15 @@
 include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
 
+function(test_multi list)
+  set(i 0)
+  foreach(value IN LISTS ${list})
+    math(EXPR j "${i} + 1")
+    set(${list}[${i}] "${value}")
+    TEST(${list}[${i}] "${ARGV${j}}")
+    set(i ${j})
+  endforeach()
+endfunction()
+
 function(test1)
   cmake_parse_arguments(PARSE_ARGV 0
     pref "OPT1;OPT2" "SINGLE1;SINGLE2" "MULTI1;MULTI2")
@@ -23,8 +33,16 @@ function(test2 arg1)
   TEST(pref_OPT2 FALSE)
   TEST(pref_SINGLE1 "foo;bar")
   TEST(pref_SINGLE2 UNDEFINED)
-  TEST(pref_MULTI1 bar foo bar)
+  test_multi(pref_MULTI1 bar "foo;bar")
   TEST(pref_MULTI2 UNDEFINED)
   TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
 endfunction()
-test2("first named" OPT1 SINGLE1 "foo;bar" MULTI1 bar foo bar)
+test2("first named" OPT1 SINGLE1 "foo;bar" MULTI1 bar "foo;bar")
+
+function(test3 arg1)
+  cmake_parse_arguments(PARSE_ARGV 0
+    pref "" "" "")
+
+  test_multi(pref_UNPARSED_ARGUMENTS "foo;bar" dog cat)
+endfunction()
+test3("foo;bar" dog cat)
-- 
cgit v0.12