JDK-8347707 : Standardise the use of os::snprintf and os::snprintf_checked
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: runtime
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2025-01-14
  • Updated: 2025-09-04
  • Resolved: 2025-08-31
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 26
26 b14Fixed
Related Reports
Blocks :  
Relates :  
Relates :  
Relates :  
Description
The platform `snprintf/vsnprintf` returns -1 on error, else if the buffer is large enough returns the number of bytes written (excluding the null byte), else (buffer is too small) the  number of characters (excluding the terminating null byte) which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.

To provide a consistent approach to error handling and truncation management, we provide `os::xxx` wrapper functions as described below and forbid the use of the library `::vsnprintf` and `::snprintf`.

The potential errors are, generally speaking, not something we should encounter in our own well-written code:

- encoding error: not applicable as we are not using extended character sets
- invalid parameters (null buffers, specifying a limit > size of the buffer [Windows], things of this nature)
- mal-formed formatting directives
- overflow error (POSIX) if the required buffer size exceeds INT_MAX (as we return `int`).

As these should simply never occur, we handle the checks for -1 at the lowest-level (`os::vsnprintf`) with an assertion, and accompanying precondition assertions.

The potential clients of this API then fall into a number of camps:

1. Those who have sized their buffer correctly, don't need the return value for subsequent use, and for whom truncation (if it were possible) would be a programming error.

For these clients we have `void os::snprintf_checked` - which returns nothing and asserts on truncation.

2. Those who have sized their buffer correctly, but do need the return value for subsequent operations (e.g. chains of `snprintf` where you advance the buffer pointer based on previous writes), but again for whom truncation should never happen.

For these clients we have `os::snprintf`, but they have to add their own assertion for no truncation.

3. Those who present a buffer but know that truncation is a possibility, but don't need to do anything about it themselves, and for whom the return value is of no use.

These clients also use `os::snprintf_checked`. The truncation assertion can be useful for guiding buffer sizing decisions, but in product mode truncation is not an error.

4. Those who present a buffer but know that truncation is a possibility, and either need to handle it themselves, or else need to use the return value in subsequent operations.

These clients are also directed to use `os::snprintf`.

In summary we provide the following API:
- `[[nodiscard]] int os::vsnprintf` is the building block for the other methods, it:
  - asserts on precondition failures
  - asserts on error
  - guarantees null-termination in the case of unexpected errors (as the standards are still unclear on that point
  - is declared `[[nodiscard[]]` so that callers cannot ignore the return value (they can explicitly cast to `void` to indicate they dn't need it)
- `void os::snprintf_checked`
  - calls `os::vnsprintf`` so asserts on errors
  - asserts on truncation
- [[nodiscard]] int os::snprintf
  - calls `os::vnsprintf`` so asserts on errors

In terms of the effects on the existing code we:
- Change callers of `::snprintf`/`os::snprintf` that ignore the return value and ensure the buffer is large enough to use `os::snprintf_checked`
  - those that allow truncation to happen must use `os::snprintf`.
- Change all callers of `::snprintf`/`os::snprintf` that use the return value to use `os::snprintf`, plus any additional assertions needed
- Change the 9 callers of `os::snprintf_checked` that do use the return value, to use `os::snprintf` with their own assertions added
- Callers of `os::vnsprintf` are adjusted as needed

Comments
Changeset: 80ab094a Branch: master Author: David Holmes <dholmes@openjdk.org> Date: 2025-08-31 21:34:16 +0000 URL: https://git.openjdk.org/jdk/commit/80ab094a75a6474c33214e3347e08ea7b9177ec8
31-08-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/26849 Date: 2025-08-19 22:02:30 +0000
19-08-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/26470 Date: 2025-07-25 01:40:45 +0000
04-08-2025

[~dholmes] >> This could happen if you print output from unknown sources. Or, e.g., print the output of strerror, which is localized. >But localized output from strerror would not produce an encoding error would it? I've tried in vain to find concrete examples of encoding errors that don't involve wide-characters (ie using format specifiers like %ls (wide string) or %lc (wide character) ). Yes, now that you mention it, I remember trying to provoke an encoding error once and failing. Maybe it's not an issue. >> I argue that such code should not be written anymore, > Perhaps. Feel free to file RFE's to convert such cases to using stringStream. https://bugs.openjdk.org/browse/JDK-8364103
25-07-2025

> I like this plan. Thanks for taking a look [~stuefe] > This could happen if you print output from unknown sources. Or, e.g., print the output of strerror, which is localized. But localized output from strerror would not produce an encoding error would it? I've tried in vain to find concrete examples of encoding errors that don't involve wide-characters (ie using format specifiers like %ls (wide string) or %lc (wide character) ). > I argue that such code should not be written anymore, Perhaps. Feel free to file RFE's to convert such cases to using stringStream.
25-07-2025

I like this plan. Some remarks: ` - encoding error: not applicable as we are not using extended character sets ` This could happen if you print output from unknown sources. Or, e.g., print the output of strerror, which is localized. `2. Those who have sized their buffer correctly, but do need the return value for subsequent operations (e.g. chains of `snprintf` where you advance the buffer pointer based on previous writes), but again for whom truncation should never happen. ` I argue that such code should not be written anymore, since calculating the correct remaining buffer length is fiddly and often wrong. The better alternative is to use a stringStream wrapped over the char buffer in question, and use that for output.
25-07-2025

[~kbarrett] has formulated a plan of attack for the uses of library snprintf, os::snprintf and os::snprintf_checked. In summary: 1. Redeclare ::snprintf as [[nodiscard]] if possible; otherwise keep os::snprintf and make it [[nodiscard]] (call this "the other version") 2. Change os::snprintf_checked to be void (most callers ignore the return value anyway) 3. Change all callers of ::snprintf/os::snprintf that ignore the return value to use the os checked version 4. Change the 9 callers of the checked version that do use the return value, to use the other version 5. Change the callers of ::snprintf that use the return value, to call the other version EDIT: step 3 is not correct. Only those callers that explicitly make their buffers big enough can use the checked versions; others will fail the assert on truncation - e.g when setting the thread name at the OS level it has to be shortened for OS limitations. So every caller has to be examined.
23-07-2025

The path forward is muddied somewhat by the fact that JDK-8296812 replaced uses of the deprecated (in Xcode) sprintf with "snprintf" but because callers were assuming the return value was always a valid length a helper function os::snprintf_checked was defined instead: int os::snprintf_checked(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); assert(result >= 0, "os::snprintf error"); assert(static_cast<size_t>(result) < len, "os::snprintf truncated"); return result; } So should existing uses of os::snprintf instead be changed to os::snprintf_checked?
17-07-2025

After discussions with [~kbarrett] we have concluded that we no longer need the os::snprintf abstraction layer as all platform implementations are functionall equivalent, and that getting rid of it may be the simplest path forward.
08-07-2025

os::snprintf ensures null-termination even in the error case. ::snprintf is kind of vague about that. I also have questions about how jio_snprintf fits into this. It has some weirdness of its own. It seems to me it would be better to consistenly use only one of these in HotSpot. As it is, right now its hard to know which one should be used.
02-07-2025

IIRC the existence of os::snprintf was to ensure nul-termination because the Windows version (at least at some point in the past**) did not guarantee that - and it was for use from shared code. Within non-Windows platform specific code, using the platform snprintf was considered perfectly fine. ** From Visual Studio 2015 and Windows 10 snprintf is C99 compliant and ensures null-termination: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/snprintf-snprintf-snprintf-l-snwprintf-snwprintf-l?view=msvc-170
15-01-2025