changeset 8961:f3108e56b502

8196882: VS2017 Hotspot Defined vsnprintf Function Causes C2084 Already Defined Compilation Error Summary: Add os::vsnprintf and os::snprintf. Reviewed-by: kbarrett, lfoltan, stuefe, mlarsson
author kevinw
date Thu, 02 Aug 2018 03:54:51 -0700
parents bbd1da3f538f
children b4ee249eb1c4
files src/os/posix/vm/os_posix.cpp src/os/windows/vm/os_windows.cpp src/share/vm/gc_implementation/g1/g1GCPhaseTimes.cpp src/share/vm/oops/generateOopMap.cpp src/share/vm/prims/jni.cpp src/share/vm/prims/jvm.cpp src/share/vm/runtime/os.cpp src/share/vm/runtime/os.hpp src/share/vm/utilities/exceptions.cpp src/share/vm/utilities/globalDefinitions_visCPP.hpp src/share/vm/utilities/ostream.cpp src/share/vm/utilities/ostream.hpp
diffstat 12 files changed, 202 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/src/os/posix/vm/os_posix.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/os/posix/vm/os_posix.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
+* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -166,6 +166,16 @@
   return aligned_base;
 }
 
+int os::vsnprintf(char* buf, size_t len, const char* fmt, va_list args) {
+  int result = ::vsnprintf(buf, len, fmt, args);
+  // If an encoding error occurred (result < 0) then it's not clear
+  // whether the buffer is NUL terminated, so ensure it is.
+  if ((result < 0) && (len > 0)) {
+    buf[len - 1] = '\0';
+  }
+  return result;
+}
+
 void os::Posix::print_load_average(outputStream* st) {
   st->print("load average:");
   double loadavg[3];
--- a/src/os/windows/vm/os_windows.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/os/windows/vm/os_windows.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -1837,6 +1837,42 @@
   st->cr();
 }
 
+
+int os::vsnprintf(char* buf, size_t len, const char* fmt, va_list args) {
+#if _MSC_VER >= 1900
+  // Starting with Visual Studio 2015, vsnprint is C99 compliant.
+  int result = ::vsnprintf(buf, len, fmt, args);
+  // If an encoding error occurred (result < 0) then it's not clear
+  // whether the buffer is NUL terminated, so ensure it is.
+  if ((result < 0) && (len > 0)) {
+    buf[len - 1] = '\0';
+  }
+  return result;
+#else
+  // Before Visual Studio 2015, vsnprintf is not C99 compliant, so use
+  // _vsnprintf, whose behavior seems to be *mostly* consistent across
+  // versions.  However, when len == 0, avoid _vsnprintf too, and just
+  // go straight to _vscprintf.  The output is going to be truncated in
+  // that case, except in the unusual case of empty output.  More
+  // importantly, the documentation for various versions of Visual Studio
+  // are inconsistent about the behavior of _vsnprintf when len == 0,
+  // including it possibly being an error.
+  int result = -1;
+  if (len > 0) {
+    result = _vsnprintf(buf, len, fmt, args);
+    // If output (including NUL terminator) is truncated, the buffer
+    // won't be NUL terminated.  Add the trailing NUL specified by C99.
+    if ((result < 0) || (result >= (int) len)) {
+      buf[len - 1] = '\0';
+    }
+  }
+  if (result < 0) {
+    result = _vscprintf(fmt, args);
+  }
+  return result;
+#endif // _MSC_VER dispatch
+}
+
 void os::print_signal_handlers(outputStream* st, char* buf, size_t buflen) {
   // do nothing
 }
--- a/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/gc_implementation/g1/g1GCPhaseTimes.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -41,13 +41,13 @@
   int _cur;
 
   void vappend(const char* format, va_list ap)  ATTRIBUTE_PRINTF(2, 0) {
-    int res = vsnprintf(&_buffer[_cur], BUFFER_LEN - _cur, format, ap);
-    if (res != -1) {
-      _cur += res;
-    } else {
+    int res = os::vsnprintf(&_buffer[_cur], BUFFER_LEN - _cur, format, ap);
+    if (res > BUFFER_LEN) {
       DEBUG_ONLY(warning("buffer too small in LineBuffer");)
       _buffer[BUFFER_LEN -1] = 0;
       _cur = BUFFER_LEN; // vsnprintf above should not add to _buffer if we are called again
+    } else if (res != -1) {
+      _cur += res;
     }
   }
 
--- a/src/share/vm/oops/generateOopMap.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/oops/generateOopMap.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@
 #include "oops/symbol.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/java.hpp"
+#include "runtime/os.hpp"
 #include "runtime/relocator.hpp"
 #include "utilities/bitMap.inline.hpp"
 #include "prims/methodHandles.hpp"
@@ -2133,10 +2134,10 @@
 void GenerateOopMap::error_work(const char *format, va_list ap) {
   _got_error = true;
   char msg_buffer[512];
-  vsnprintf(msg_buffer, sizeof(msg_buffer), format, ap);
+  os::vsnprintf(msg_buffer, sizeof(msg_buffer), format, ap);
   // Append method name
   char msg_buffer2[512];
-  jio_snprintf(msg_buffer2, sizeof(msg_buffer2), "%s in method %s", msg_buffer, method()->name()->as_C_string());
+  os::snprintf(msg_buffer2, sizeof(msg_buffer2), "%s in method %s", msg_buffer, method()->name()->as_C_string());
   _exception = Exceptions::new_exception(Thread::current(),
                 vmSymbols::java_lang_LinkageError(), msg_buffer2);
 }
@@ -2154,7 +2155,7 @@
   _got_error = true;
   // Append method name
   char msg_buffer2[512];
-  jio_snprintf(msg_buffer2, sizeof(msg_buffer2), "%s in method %s", msg,
+  os::snprintf(msg_buffer2, sizeof(msg_buffer2), "%s in method %s", msg,
                method()->name()->as_C_string());
   _exception = Exceptions::new_exception(Thread::current(),
                 vmSymbols::java_lang_LinkageError(), msg_buffer2);
--- a/src/share/vm/prims/jni.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/prims/jni.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -33,6 +33,7 @@
 #include "classfile/vmSymbols.hpp"
 #include "interpreter/linkResolver.hpp"
 #include "utilities/macros.hpp"
+#include "utilities/ostream.hpp"
 #if INCLUDE_ALL_GCS
 #include "gc_implementation/g1/g1SATBCardTableModRefBS.hpp"
 #endif // INCLUDE_ALL_GCS
@@ -5127,6 +5128,7 @@
     run_unit_test(GuardedMemory::test_guarded_memory());
     run_unit_test(AltHashing::test_alt_hash());
     run_unit_test(test_loggc_filename());
+    run_unit_test(test_snprintf());
     run_unit_test(TestNewSize_test());
     run_unit_test(TestKlass_test());
     run_unit_test(Test_linked_list());
--- a/src/share/vm/prims/jvm.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/prims/jvm.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -2915,16 +2915,12 @@
 
 ATTRIBUTE_PRINTF(3, 0)
 int jio_vsnprintf(char *str, size_t count, const char *fmt, va_list args) {
-  // see bug 4399518, 4417214
+  // Reject count values that are negative signed values converted to
+  // unsigned; see bug 4399518, 4417214
   if ((intptr_t)count <= 0) return -1;
 
-  int result = vsnprintf(str, count, fmt, args);
-  // Note: on truncation vsnprintf(3) on Unix returns number of
-  // characters which would have been written had the buffer been large
-  // enough; on Windows, it returns -1. We handle both cases here and
-  // always return -1, and perform null termination.
-  if ((result > 0 && (size_t)result >= count) || result == -1) {
-    str[count - 1] = '\0';
+  int result = os::vsnprintf(str, count, fmt, args);
+  if (result > 0 && (size_t)result >= count) {
     result = -1;
   }
 
--- a/src/share/vm/runtime/os.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/runtime/os.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -108,6 +108,14 @@
 #endif
 }
 
+int os::snprintf(char* buf, size_t len, const char* fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  int result = os::vsnprintf(buf, len, fmt, args);
+  va_end(args);
+  return result;
+}
+
 // Fill in buffer with current local time as an ISO-8601 string.
 // E.g., yyyy-mm-ddThh:mm:ss-zzzz.
 // Returns buffer, or NULL if it failed.
--- a/src/share/vm/runtime/os.hpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/runtime/os.hpp	Thu Aug 02 03:54:51 2018 -0700
@@ -616,6 +616,9 @@
   static void *find_agent_function(AgentLibrary *agent_lib, bool check_lib,
                                    const char *syms[], size_t syms_len);
 
+  static int vsnprintf(char* buf, size_t len, const char* fmt, va_list args) ATTRIBUTE_PRINTF(3, 0);
+  static int snprintf(char* buf, size_t len, const char* fmt, ...) ATTRIBUTE_PRINTF(3, 4);
+
   // Print out system information; they are called by fatal error handler.
   // Output format may be different on different platforms.
   static void print_os_info(outputStream* st);
--- a/src/share/vm/utilities/exceptions.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/utilities/exceptions.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -30,6 +30,7 @@
 #include "runtime/init.hpp"
 #include "runtime/java.hpp"
 #include "runtime/javaCalls.hpp"
+#include "runtime/os.hpp"
 #include "runtime/thread.inline.hpp"
 #include "runtime/threadCritical.hpp"
 #include "utilities/events.hpp"
@@ -246,8 +247,7 @@
   va_list ap;
   va_start(ap, format);
   char msg[max_msg_size];
-  vsnprintf(msg, max_msg_size, format, ap);
-  msg[max_msg_size-1] = '\0';
+  os::vsnprintf(msg, max_msg_size, format, ap);
   va_end(ap);
   _throw_msg(thread, file, line, h_name, msg);
 }
--- a/src/share/vm/utilities/globalDefinitions_visCPP.hpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/utilities/globalDefinitions_visCPP.hpp	Thu Aug 02 03:54:51 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -187,14 +187,6 @@
 #pragma warning( disable : 4996 ) // unsafe string functions. Same as define _CRT_SECURE_NO_WARNINGS/_CRT_SECURE_NO_DEPRICATE
 #endif
 
-inline int vsnprintf(char* buf, size_t count, const char* fmt, va_list argptr) {
-  // If number of characters written == count, Windows doesn't write a
-  // terminating NULL, so we do it ourselves.
-  int ret = _vsnprintf(buf, count, fmt, argptr);
-  if (count > 0) buf[count-1] = '\0';
-  return ret;
-}
-
 // Portability macros
 #define PRAGMA_INTERFACE
 #define PRAGMA_IMPLEMENTATION
--- a/src/share/vm/utilities/ostream.cpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/utilities/ostream.cpp	Thu Aug 02 03:54:51 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 #include "gc_implementation/shared/gcId.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/arguments.hpp"
+#include "runtime/os.hpp"
 #include "utilities/defaultStream.hpp"
 #include "utilities/ostream.hpp"
 #include "utilities/top.hpp"
@@ -89,6 +90,8 @@
                                        const char* format, va_list ap,
                                        bool add_cr,
                                        size_t& result_len) {
+  assert(buflen >= 2, "buffer too small");
+
   const char* result;
   if (add_cr)  buflen--;
   if (!strchr(format, '%')) {
@@ -101,18 +104,20 @@
     result = va_arg(ap, const char*);
     result_len = strlen(result);
     if (add_cr && result_len >= buflen)  result_len = buflen-1;  // truncate
-  } else if (vsnprintf(buffer, buflen, format, ap) >= 0) {
+  } else {
+    int written = os::vsnprintf(buffer, buflen, format, ap);
+    assert(written >= 0, "vsnprintf encoding error");
     result = buffer;
-    result_len = strlen(result);
-  } else {
-    DEBUG_ONLY(warning("increase O_BUFLEN in ostream.hpp -- output truncated");)
-    result = buffer;
-    result_len = buflen - 1;
-    buffer[result_len] = 0;
+    if ((size_t)written < buflen) {
+      result_len = written;
+    } else {
+      DEBUG_ONLY(warning("increase O_BUFLEN in ostream.hpp -- output truncated");)
+      result_len = buflen - 1;
+    }
   }
   if (add_cr) {
     if (result != buffer) {
-      strncpy(buffer, result, buflen);
+      memcpy(buffer, result, result_len);
       result = buffer;
     }
     buffer[result_len++] = '\n';
@@ -578,6 +583,117 @@
     assert(o_result == NULL, err_msg("Too long file name after pid expansion should return NULL, but got '%s'", o_result));
   }
 }
+
+//////////////////////////////////////////////////////////////////////////////
+// Test os::vsnprintf and friends.
+
+void check_snprintf_result(int expected, size_t limit, int actual, bool expect_count) {
+  if (expect_count || ((size_t)expected < limit)) {
+    assert(expected == actual, "snprintf result not expected value");
+  } else {
+    // Make this check more permissive for jdk8u, don't assert that actual == 0.
+    // e.g. jio_vsnprintf_wrapper and jio_snprintf return -1 when expected >= limit
+    if (expected >= (int) limit) {
+      assert(actual == -1, "snprintf result should be -1 for expected >= limit");
+    } else {
+      assert(actual > 0, "snprintf result should be >0 for expected < limit");
+    }
+  }
+}
+
+// PrintFn is expected to be int (*)(char*, size_t, const char*, ...).
+// But jio_snprintf is a C-linkage function with that signature, which
+// has a different type on some platforms (like Solaris).
+template<typename PrintFn>
+void test_snprintf(PrintFn pf, bool expect_count) {
+  const char expected[] = "abcdefghijklmnopqrstuvwxyz";
+  const int expected_len = sizeof(expected) - 1;
+  const size_t padding_size = 10;
+  char buffer[2 * (sizeof(expected) + padding_size)];
+  char check_buffer[sizeof(buffer)];
+  const char check_char = '1';  // Something not in expected.
+  memset(check_buffer, check_char, sizeof(check_buffer));
+  const size_t sizes_to_test[] = {
+    sizeof(buffer) - padding_size,       // Fits, with plenty of space to spare.
+    sizeof(buffer)/2,                    // Fits, with space to spare.
+    sizeof(buffer)/4,                    // Doesn't fit.
+    sizeof(expected) + padding_size + 1, // Fits, with a little room to spare
+    sizeof(expected) + padding_size,     // Fits exactly.
+    sizeof(expected) + padding_size - 1, // Doesn't quite fit.
+    2,                                   // One char + terminating NUL.
+    1,                                   // Only space for terminating NUL.
+    0 };                                 // No space at all.
+  for (unsigned i = 0; i < ARRAY_SIZE(sizes_to_test); ++i) {
+    memset(buffer, check_char, sizeof(buffer)); // To catch stray writes.
+    size_t test_size = sizes_to_test[i];
+    ResourceMark rm;
+    stringStream s;
+    s.print("test_size: " SIZE_FORMAT, test_size);
+    size_t prefix_size = padding_size;
+    guarantee(test_size <= (sizeof(buffer) - prefix_size), "invariant");
+    size_t write_size = MIN2(sizeof(expected), test_size);
+    size_t suffix_size = sizeof(buffer) - prefix_size - write_size;
+    char* write_start = buffer + prefix_size;
+    char* write_end = write_start + write_size;
+
+    int result = pf(write_start, test_size, "%s", expected);
+
+    check_snprintf_result(expected_len, test_size, result, expect_count);
+
+    // Verify expected output.
+    if (test_size > 0) {
+      assert(0 == strncmp(write_start, expected, write_size - 1), "strncmp failure");
+      // Verify terminating NUL of output.
+      assert('\0' == write_start[write_size - 1], "null terminator failure");
+    } else {
+      guarantee(test_size == 0, "invariant");
+      guarantee(write_size == 0, "invariant");
+      guarantee(prefix_size + suffix_size == sizeof(buffer), "invariant");
+      guarantee(write_start == write_end, "invariant");
+    }
+
+    // Verify no scribbling on prefix or suffix.
+    assert(0 == strncmp(buffer, check_buffer, prefix_size), "prefix scribble");
+    assert(0 == strncmp(write_end, check_buffer, suffix_size), "suffix scribble");
+  }
+
+  // Special case of 0-length buffer with empty (except for terminator) output.
+  check_snprintf_result(0, 0, pf(NULL, 0, "%s", ""), expect_count);
+  check_snprintf_result(0, 0, pf(NULL, 0, ""), expect_count);
+}
+
+// This is probably equivalent to os::snprintf, but we're being
+// explicit about what we're testing here.
+static int vsnprintf_wrapper(char* buf, size_t len, const char* fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  int result = os::vsnprintf(buf, len, fmt, args);
+  va_end(args);
+  return result;
+}
+
+// These are declared in jvm.h; test here, with related functions.
+extern "C" {
+int jio_vsnprintf(char*, size_t, const char*, va_list);
+int jio_snprintf(char*, size_t, const char*, ...);
+}
+
+// This is probably equivalent to jio_snprintf, but we're being
+// explicit about what we're testing here.
+static int jio_vsnprintf_wrapper(char* buf, size_t len, const char* fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  int result = jio_vsnprintf(buf, len, fmt, args);
+  va_end(args);
+  return result;
+}
+
+void test_snprintf() {
+  test_snprintf(vsnprintf_wrapper, true);
+  test_snprintf(os::snprintf, true);
+  test_snprintf(jio_vsnprintf_wrapper, false); // jio_vsnprintf returns -1 on error including exceeding buffer size
+  test_snprintf(jio_snprintf, false);          // jio_snprintf calls jio_vsnprintf
+}
 #endif // PRODUCT
 
 fileStream::fileStream(const char* file_name) {
--- a/src/share/vm/utilities/ostream.hpp	Wed Aug 01 04:19:22 2018 -0400
+++ b/src/share/vm/utilities/ostream.hpp	Thu Aug 02 03:54:51 2018 -0700
@@ -258,6 +258,7 @@
 #ifndef PRODUCT
 // unit test for checking -Xloggc:<filename> parsing result
 void test_loggc_filename();
+void test_snprintf();
 #endif
 
 void ostream_init();