libmapper: fix logic bug

sarraylen returns the number of strings and does not include the
trailing null pointer.  This results in sarraydup creating arrays of
strings where the array is not null pointer terminated (the strings
themselves _are_ \0 terminated), which in turn causes random data to be
passed to free.

The bug was found using static analysis and inspection would indicate
exposure only exists on error and shutdown paths, which might be a hint
as to how this has been lurking for as long as it has.

Change-Id: Ie5e5b6cdfb7832c84037ff039b41082fc7d20b61
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/libmapper/test/Makefile.am.include b/libmapper/test/Makefile.am.include
new file mode 100644
index 0000000..fdd4849
--- /dev/null
+++ b/libmapper/test/Makefile.am.include
@@ -0,0 +1,5 @@
+libmapper_test_mapper_SOURCES = %reldir%/mapper.cpp \
+		libmapper/mapper.c libmapper/test/utils.c
+
+check_PROGRAMS += \
+	%reldir%/mapper
diff --git a/libmapper/test/mapper.cpp b/libmapper/test/mapper.cpp
new file mode 100644
index 0000000..8831b62
--- /dev/null
+++ b/libmapper/test/mapper.cpp
@@ -0,0 +1,22 @@
+#include "../internal.h"
+#include "utils.h"
+
+#include <gtest/gtest.h>
+
+TEST(TestSarray, Dup)
+{
+    auto a = generate_test_sarray(3);
+
+    auto size = sarraylen(a);
+    EXPECT_EQ(size, 3);
+
+    auto b = sarraydup(a);
+    size_t i;
+
+    for (i = 0; i < 4; i++)
+    {
+        EXPECT_STREQ(a[i], b[i]);
+    }
+    sarrayfree(a);
+    sarrayfree(b);
+}
diff --git a/libmapper/test/utils.c b/libmapper/test/utils.c
new file mode 100644
index 0000000..b3c7ae1
--- /dev/null
+++ b/libmapper/test/utils.c
@@ -0,0 +1,31 @@
+#include "utils.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+char** generate_test_sarray(size_t len)
+{
+    static const char testString[] = "test";
+    size_t i;
+    char** ret = calloc(len + 1, sizeof(*ret));
+    if (!ret)
+        return NULL;
+
+    for (i = 0; i < len; ++i)
+    {
+        ret[i] = strdup(testString);
+        if (!ret[i])
+            goto error;
+    }
+
+    return ret;
+
+error:
+    for (i = 0; i < len; ++i)
+    {
+        free(ret[i]);
+    }
+    free(ret);
+
+    return NULL;
+}
diff --git a/libmapper/test/utils.h b/libmapper/test/utils.h
new file mode 100644
index 0000000..73162cb
--- /dev/null
+++ b/libmapper/test/utils.h
@@ -0,0 +1,9 @@
+#pragma once
+#include <stddef.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+char** generate_test_sarray(size_t);
+#ifdef __cplusplus
+}
+#endif