One more Windows Unicode PR: do not use %S#1398
Conversation
gasche
left a comment
There was a problem hiding this comment.
If the function may truncate its input (and has a special memory-ownship behavior), it should be clear in the name. Currently the name suggests a normal conversion function, that (1) creates a new caller-owned value and (2) is injective.
I'm not fully convinced that this solution is an improvement over the direct style of having a conversion call before the print and a free call after the print. This is a burden at the callsite, but it is a normal kind of burden for C programmers, whereas you alleviate the issue by introducing a convenient but unusual way to shoot oneself in the foot (if someday the usage is extended to a case where truncation is problematic; and truncation has proved problematic in other places of the runtime before, eg. executable_name).
One aspect of the explicit-free solution that is unsatisfying is that error functions (caml_fatal_error_arg) never return and would thus never freed the copied strings (which is inconvenient if you look for leaks using valgrind, etc.). But since bf70eb9 we explicitly track caml_stat_alloc memory, so using a static allocation should not be an issue there.
byterun/win32.c
Outdated
|
|
||
| CAMLexport char * win_utf8_string_of_utf16(const wchar_t * s) | ||
| { | ||
| static char buf[1024]; |
There was a problem hiding this comment.
Is this use of global state correct in a multicore runtime? (Would the static buffer be copied by each runtime instance, which is fine, or is there a risk of race to a single buffer?)
There was a problem hiding this comment.
This was an alarm bell for me - I think @stedolan will confirm that we should be avoiding adding static buffers unless absolutely necessary.
There was a problem hiding this comment.
Well spotted, and yes, this would be bad in multicore. (I have some hacky scripts to detect such buffers, but they only work on ELF binaries. I'm not sure how to prevent them sneaking into windows-only codepaths, other than by vigilance)
byterun/caml/osdeps.h
Outdated
| The returned string points to a fixed-length, statically-allocated buffer so | ||
| make sure to copy it if it needs to be preserved. Also, due to the fixed | ||
| length of the buffer, the result may be truncated. */ | ||
| extern char * win_utf8_string_of_utf16(const wchar_t *s); |
There was a problem hiding this comment.
The function name in the documentation comment does not match the exposed name.
There was a problem hiding this comment.
The function no longer matches the semantics of the comment, either!
|
Thanks for the review @gasche! I agree that my approach may be more complex/error-prone than needed. I will amend by explicitly allocating and freeing the converted string at the |
|
Just about to review this too - @gasche, please could we hold off beta2 until a decision is taken on this one. The code page setting part of this GPR I would very much like to be in (I'm preparing a larger post on the Unicode alterations for Windows) |
sure (I have to discuss it with Damien first but I'm thinking of trying a "beta early, beta often" approach this time, which means that I could not wait and just do another beta when you ask for one.) |
|
What's the current behavior and how bad it is? If it's only about displaying garbage for non-ASCII characters in filenames in case of runtime debug messages or fatal errors, this does not sound a critical bug, and it might be better to avoid any risk of regression or rushed solution for 4.06. |
dra27
left a comment
There was a problem hiding this comment.
The previous code you showed me had some changes in the debugger - have you intentionally reverted those?
asmrun/startup.c
Outdated
| return Val_unit; | ||
|
|
||
| #if defined(_WIN32) && WINDOWS_UNICODE | ||
| SetConsoleOutputCP(CP_UTF8); |
There was a problem hiding this comment.
It would be worth a comment noting that this affects all output functions (i.e. both stdout and stderr), I think.
Two possible ideas:
- Selecting CP_UTF8 only works if a TrueType font is selected (legacy Raster Fonts won't work, which is still the default case pre Windows 8, IIRC). You can detect this using this function
int console_supports_unicode (void) {
CONSOLE_FONT_INFOEX fontInfo;
fontInfo.cbSize = sizeof(fontInfo);
if (GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &fontInfo) && !(fontInfo.FontFamily & TMPF_TRUETYPE)) {
return (GetCurrentConsoleFontEx(GetStdHandle(STD_ERROR_HANDLE), FALSE, &fontInfo) && (fontInfo.FontFamily & TMPF_TRUETYPE));
} else {
return 1;
}
}- Having done that, it might be worth the addition of a function in
Syswhich exposes this (a bit likeUnix.has_symlink)
byterun/caml/misc.h
Outdated
| #define caml_stat_strdup_to_os caml_stat_strdup | ||
| #define caml_stat_strdup_of_os caml_stat_strdup | ||
| #define caml_copy_string_of_os caml_copy_string | ||
| #define caml_utf8_string_of_os(s) s |
There was a problem hiding this comment.
I think convention says the s should be (s) to ensure that you don't change the meaning of an expression, although I think you'd have to be doing something seriously contrived to manage that with a string pointer.
byterun/caml/osdeps.h
Outdated
| The returned string points to a fixed-length, statically-allocated buffer so | ||
| make sure to copy it if it needs to be preserved. Also, due to the fixed | ||
| length of the buffer, the result may be truncated. */ | ||
| extern char * win_utf8_string_of_utf16(const wchar_t *s); |
There was a problem hiding this comment.
The function no longer matches the semantics of the comment, either!
byterun/dynlink.c
Outdated
| realname = caml_search_dll_in_path(&caml_shared_libs_path, name); | ||
| caml_gc_message(0x100, "Loading shared library %" | ||
| ARCH_CHARNATSTR_PRINTF_FORMAT "\n", realname); | ||
| caml_gc_message(0x100, "Loading shared library %s\n", caml_utf8_string_of_os(realname)); |
There was a problem hiding this comment.
I agree with @gasche's concern about using a static buffer. You cover nearly all cases of the strdup with either caml_fatal_arg (which will automatically clean up caml_stat_strdup anyway) or caml_gc_message. You could introduce caml_gc_message_free which strictly takes a string parameter and frees it after displaying the message.
byterun/startup.c
Outdated
| return; | ||
|
|
||
| #if defined(_WIN32) && WINDOWS_UNICODE | ||
| SetConsoleOutputCP(CP_UTF8); |
There was a problem hiding this comment.
See similar comment from asmrun/startup.c
byterun/startup.c
Outdated
| return Val_unit; | ||
|
|
||
| #if defined(_WIN32) && WINDOWS_UNICODE | ||
| SetConsoleOutputCP(CP_UTF8); |
byterun/sys.c
Outdated
| } else { | ||
| fprintf(stderr, "Cannot load C plugin %s\nReason: %s\n", | ||
| caml_stat_strdup_of_os(plugin), caml_dlerror()); | ||
| caml_utf8_string_of_os(plugin), caml_dlerror()); |
There was a problem hiding this comment.
This would require a manual free if caml_utf8_string_of_os starts allocating.
|
The current behavior is that it will either display garbage or even nothing at all (depending on Console code page, etc) for non-ASCII characters in those places where |
|
The |
|
Note that running |
What about the scenario where the OCaml runtime is embedded in a host application? Could this negatively affect outputs of this application? |
|
Compiled in legacy mode, no difference at all; compiled with WINDOWS_UNICODE=1, the Console would start correctly displaying UTF-8 sequences as the characters they represent - such an application would definitely have been displaying garbage before. It definitely only affects the console, not any file translations. |
|
(it also doesn't affect input processing, which is SetConsoleCP) |
|
I pushed one commit changing to explicitly allocation/deallocation around the few
|
|
|
||
| realname = caml_search_dll_in_path(&caml_shared_libs_path, name); | ||
| caml_gc_message(0x100, "Loading shared library %" | ||
| ARCH_CHARNATSTR_PRINTF_FORMAT "\n", realname); |
There was a problem hiding this comment.
Unrelated to the PR, but am I correct that ARCH_CHARNATSTR_PRINTF_FORMAT is a new 4.06 thing that escaped the big os rename? (Will this PR end up removing it?)
|
I have the feeling that we are converging on the matter of how printf stuff should handle the string conversion, but that the ConsoleCP stuff requires another level of expertise and is somewhat orthogonal (although in the end we need to have both for proper Unicode printing, if I understand correctly). @nojb, you may consider splitting the PR in two parts -- but feel free to keep only one if that's easier for you. |
| { | ||
| int retcode = win_wide_char_to_multi_byte_noexc(s, slen, out, outlen); | ||
|
|
||
| if (retcode == 0) |
There was a problem hiding this comment.
This code is almost certainly now eliminated, but this should be if (retcode == 0 && slen != 0)
There was a problem hiding this comment.
Indeed, this was the cause for a previous AppVeyor failure, but is now gone.
There was a problem hiding this comment.
(that was causing your previous AppVeyor failure, since the building of dynlink includes -ccopt "" so you were hitting the case of an empty string being passed to win_wide_char_to_multi_byte)
|
@nojb - cosmetically, it would be that we only tell the console to switch to UTF-8 output if it can actually have a chance of displaying the symbols. It would certainly be useful to display that to the user. Cosmetically, setting CP_UTF8 when a truetype font is not selected for the console should cause no change in the output (you just get garbage UTF-8). It certainly doesn't have to be done in this PR at all, therefore. @gasche - SetConsoleCP only affects input, we get proper Unicode output on the console without doing that. |
|
I don't That experiment is enough to suggest that we shouldn't change the way input is being handled - SetConsoleOutputCP is only ever going to affect what is displayed to the user, it's not going (potentially) to change the semantic behaviour of programs. |
|
@alainfrisch Re embedded runtime situation, my understanding is as follows:
@dra27 do you agree? |
|
@nojb - yes, that's my understanding of it. |
|
@dra27 I added a comment to the I think we addressed the other points as well, so in principle I consider this PR ready to be merged. |
|
Would you rebase the PR to remove some back-and-forth, but preferably keep the console-setting and %S-business in separate commits? @dra27: do you approve the current state? Do you think we should cherry-pick in 4.06? |
|
Rebased. |
|
I approve the first and third commits :-) |
|
I approve the second commit :-) Yes, I think we should cherry-pick this to 4.06 - to a certain extent, it's all part of the "big picture" of Windows Unicode support. |
|
Still investigating some issues (especially around MPR#6925) with regard to the |
|
OK, for simplicity I will simply remove commit 2 from this PR, and we can open a new PR for it as needed. |
|
OK, done (and renamed PR for accuracy). |
One more Windows Unicode PR: do not use %S (cherry picked from commit 6aae29d)
|
Merged in trunk and cherry-picked in 4.06 ( d8e0921 ). (There was really no other choice than cherry-picking as this commit removes an API that we don't want to release.) |
This PR addresses the following small issue left over from #1200. In that PR there are a handful of uses of the
printfformat specifier%Sto print wide strings (wchar_t *), mostly in calls tocaml_gc_messageto print filename arguments (see e.g. here and here).The specifier
%Sis both POSIX and conforms to the Unix specification. Unfortunately it is effectively broken under Windows (in spite of "supporting" it). The details are a little long but see this post for some good pointers.Hence we would like to get rid of the
%Sspecifiers. The only issue is that if we convert the wide strings into usual strings with our existing functions then we must not forget tofreethem, etc. Also it would be nicer not to allocate when printing GC debug messages.This PR defines a function
win_utf8_string_of_utf16inwin32.cwhich converts as needed but uses a fixed-length statically-allocated buffer to store the resulting UTF-8 string. This is not an issue because in each use of the function the result of doing the conversion does not need to be stored anywhere (and possible truncation is OK).We also use this function for some arguments to
caml_fatal_error_argwhich were being "leaked" (just before the program ends).Together with this change, we also add a call to
SetConsoleOutputCPin the differentcaml_{main,startup*}functions so that, under Windows with Unicode mode enabled, the console code page is set to UTF-8, saving the user from having to do it. This helps to print things correctly on the Windows Console (e.g. if you run your programs from the Command Prompt).Lastly, and for the record, as far as I know, the most "native" solution to these printing issues would be to use the Windows call
WriteConsole(like we do inheadernt.c) to print wide strings, which would not require us to do any conversion. However, it is not straightforward to integrate this function in ourprintf-based functionscaml_gc_message,caml_fatal_error_arg, andcaml_fatal_error_arg2.@dra27