int vs12_snprintf(char *buffer, size_t count, const char *format, ...)
{
- va_list args;
+ // These three values are invalid when calling _vsnprintf
+ if ((buffer == NULL) || (count == 0) || (format == NULL))
+ {
+ return -1;
+ }
+ // _vsnprintf behaves similarly to snprintf, but:
+ //
+ // - It doesn't always add a zero terminator to the output buffer
+ // - When the output buffer is too small, it returns -1 instead of the
+ // required buffer size
+ va_list args;
va_start(args, format);
-
int length = _vsnprintf(buffer, count, format, args);
-
va_end(args);
- if (length > count)
+ if ((length == -1) || (length == count))
{
+ // Add the missing zero character terminator.
buffer[count - 1] = '\0';
+
+ // When length == -1, it would be good to compute the required output
+ // buffer size, and return that computed value instead of -1. That would
+ // bring the behavior of vs12_snprintf closer to the C99 version of
+ // snprintf. However, IoTivity doesn't have an easy way to compute the
+ // required output buffer size. Also, there are no known callers of
+ // snprintf in IoTivity that are relying on a compliant return value,
+ // when that compliant return value would be larger than the 'count'
+ // input parameter.
+ //
+ // @todo Compute and return the output buffer size, instead of -1,
+ // when the output buffer was too small.
}
return length;
}
-
--- /dev/null
+#******************************************************************
+#
+# Copyright 2016 Microsoft
+#
+#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+import os.path
+
+######################################################################
+# Build the gtest framework
+######################################################################
+gtest_env = SConscript('#extlibs/gtest/SConscript')
+
+c_common_windows_test_env = gtest_env.Clone()
+
+######################################################################
+# Build flags
+######################################################################
+c_common_windows_test_env.PrependUnique(CPPPATH = ['../include'])
+
+c_common_windows_test_env.AppendUnique(LIBPATH = [os.path.join(c_common_windows_test_env.get('BUILD_DIR'), 'resource', 'c_common')])
+c_common_windows_test_env.PrependUnique(LIBS = ['win_helper'])
+
+if c_common_windows_test_env.get('LOGGING'):
+ c_common_windows_test_env.AppendUnique(CPPDEFINES = ['TB_LOG'])
+
+######################################################################
+# Source files and Targets
+######################################################################
+c_common_windows_tests = c_common_windows_test_env.Program('c_common_windows_tests', ['snprintf_test.cpp'])
+
+Alias("test", [c_common_windows_tests])
+
+c_common_windows_test_env.AppendTarget('test')
+
+from tools.scons.RunTest import *
+run_test(c_common_windows_test_env,
+ 'resource_c_common_windows_test.memcheck',
+ 'resource/c_common/windows/test/c_common_windows_tests')
--- /dev/null
+/* *****************************************************************
+ *
+ * Copyright 2016 Microsoft
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * *****************************************************************/
+
+#include <windows.h>
+#include <gtest/gtest.h>
+#include "vs12_snprintf.h"
+
+void OutputBufferLargerThanNeeded(size_t outputBufferSize, size_t inputBufferSize, int callerLineNumber)
+{
+ ASSERT_GT(outputBufferSize, inputBufferSize);
+ ASSERT_GT(inputBufferSize, 1);
+
+ char *inputBuffer = new char[inputBufferSize];
+ memset(inputBuffer, 'i', inputBufferSize - 1);
+ inputBuffer[inputBufferSize - 1] = '\0';
+
+ char *outputBuffer = new char[outputBufferSize];
+ memset(outputBuffer, 'o', outputBufferSize);
+
+ EXPECT_EQ((inputBufferSize - 1),
+ vs12_snprintf(outputBuffer, outputBufferSize, "%s", inputBuffer))
+ << "caller line number: " << callerLineNumber;
+ EXPECT_STREQ(inputBuffer, outputBuffer) << "caller line number: " << callerLineNumber;
+
+ delete[] inputBuffer;
+ delete[] outputBuffer;
+}
+
+void OutputBufferSmallerThanNeeded(size_t outputBufferSize, size_t inputBufferSize, int callerLineNumber)
+{
+ ASSERT_GT(inputBufferSize, outputBufferSize);
+ ASSERT_GT(outputBufferSize, 1);
+
+ char *inputBuffer = new char[inputBufferSize];
+ memset(inputBuffer, 'i', inputBufferSize - 1);
+ inputBuffer[inputBufferSize - 1] = '\0';
+
+ char *outputBuffer = new char[outputBufferSize];
+ memset(outputBuffer, 'o', outputBufferSize);
+
+ if (outputBufferSize == (inputBufferSize - 1))
+ {
+ // When the output buffer size is just off by 1, the return value of
+ // vs12_snprintf is C99-compliant.
+ EXPECT_EQ((inputBufferSize - 1),
+ vs12_snprintf(outputBuffer, outputBufferSize, "%s", inputBuffer))
+ << "caller line number: " << callerLineNumber;
+ }
+ else
+ {
+ // vs12_snprintf returns -1 in this case (a non C99-compliant value).
+ EXPECT_EQ(-1,
+ vs12_snprintf(outputBuffer, outputBufferSize, "%s", inputBuffer))
+ << "caller line number: " << callerLineNumber;
+ }
+
+ // snprintf output should be zero-terminated, even when the output buffer was too small.
+ EXPECT_EQ('\0', outputBuffer[outputBufferSize - 1]) << "caller line number: " << callerLineNumber;
+
+ // Truncate the input buffer, then compare it with the output buffer.
+ inputBuffer[outputBufferSize - 1] = '\0';
+ EXPECT_STREQ(inputBuffer, outputBuffer) << "caller line number: " << callerLineNumber;
+
+ delete[] inputBuffer;
+ delete[] outputBuffer;
+}
+
+TEST(vs12_snprintfTest, OutputBufferLargerThanNeeded1)
+{
+ OutputBufferLargerThanNeeded(10, 10 - 1, __LINE__);
+}
+
+TEST(vs12_snprintfTest, OutputBufferLargerThanNeeded2)
+{
+ OutputBufferLargerThanNeeded(123, 123 - 2, __LINE__);
+}
+
+TEST(vs12_snprintfTest, OutputBufferAsLargeAsNeeded)
+{
+ char outputBuffer[54];
+ char inputBuffer[sizeof(outputBuffer)];
+
+ memset(inputBuffer, 'i', sizeof(inputBuffer) - 1);
+ inputBuffer[sizeof(inputBuffer) - 1] = '\0';
+
+ memset(outputBuffer, 'o', sizeof(outputBuffer));
+
+ EXPECT_EQ((sizeof(inputBuffer) - 1),
+ vs12_snprintf(outputBuffer, sizeof(outputBuffer), "%s", inputBuffer));
+ EXPECT_STREQ(inputBuffer, outputBuffer);
+}
+
+TEST(vs12_snprintfTest, OutputBufferSmallerThanNeeded1)
+{
+ OutputBufferSmallerThanNeeded(100, 100 + 1, __LINE__);
+}
+
+TEST(vs12_snprintfTest, OutputBufferSmallerThanNeeded2)
+{
+ OutputBufferSmallerThanNeeded(123, 123 + 2, __LINE__);
+}
+
+TEST(vs12_snprintfTest, OutputBufferSmallerThanNeeded3)
+{
+ OutputBufferSmallerThanNeeded(432, 2 * 432, __LINE__);
+}