- Notifications
You must be signed in to change notification settings - Fork1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
One more Windows Unicode PR: do not use %S#1398
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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 sincebf70eb9 we explicitly trackcaml_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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The function name in the documentation comment does not match the exposed name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The previous code you showed me had some changes in the debugger - have you intentionally reverted those?
asmrun/startup.c Outdated
@@ -121,6 +125,10 @@ value caml_startup_common(char_os **argv, int pooling) | |||
if (!caml_startup_aux(pooling)) | |||
return Val_unit; | |||
#if defined(_WIN32) && WINDOWS_UNICODE | |||
SetConsoleOutputCP(CP_UTF8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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
intconsole_supports_unicode (void) {CONSOLE_FONT_INFOEXfontInfo;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 {return1; }}
- Having done that, it might be worth the addition of a function in
Sys
which exposes this (a bit likeUnix.has_symlink
)
byterun/caml/misc.h Outdated
@@ -230,6 +231,7 @@ typedef char char_os; | |||
#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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think convention says thes
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The function no longer matches the semantics of the comment, either!
byterun/dynlink.c Outdated
@@ -130,14 +130,13 @@ static void open_shared_lib(char_os * name) | |||
void * handle; | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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
@@ -326,6 +326,10 @@ CAMLexport void caml_main(char_os **argv) | |||
if (!caml_startup_aux(/* pooling */ caml_cleanup_on_exit)) | |||
return; | |||
#if defined(_WIN32) && WINDOWS_UNICODE | |||
SetConsoleOutputCP(CP_UTF8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
See similar comment from asmrun/startup.c
byterun/startup.c Outdated
@@ -452,6 +456,10 @@ CAMLexport value caml_startup_code_exn( | |||
if (!caml_startup_aux(pooling)) | |||
return Val_unit; | |||
#if defined(_WIN32) && WINDOWS_UNICODE | |||
SetConsoleOutputCP(CP_UTF8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
And again
byterun/sys.c Outdated
@@ -647,7 +647,7 @@ void caml_load_plugin(char_os *plugin) | |||
} | |||
} 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
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
|
void * handle; | ||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Unrelated to the PR, but am I correct thatARCH_CHARNATSTR_PRINTF_FORMAT
is a new 4.06 thing that escaped the bigos
rename? (Will this PR end up removing it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yes, and yes.
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. |
byterun/win32.c Outdated
CAMLexport int win_wide_char_to_multi_byte(const wchar_t *s, int slen, char *out, int outlen) | ||
{ | ||
int retcode = win_wide_char_to_multi_byte_noexc(s, slen, out, outlen); | ||
if (retcode == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This code is almost certainly now eliminated, but this should beif (retcode == 0 && slen != 0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Indeed, this was the cause for a previous AppVeyor failure, but is now gone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
(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 towin_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'thave 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 aroundMPR#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 commit6aae29d)
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
printf
format specifier%S
to print wide strings (wchar_t *
), mostly in calls tocaml_gc_message
to print filename arguments (see e.g.here andhere).The specifier
%S
is bothPOSIX and conforms to theUnix specification. Unfortunately it is effectively broken under Windows (in spite of"supporting" it). The details are a little long but see thispost for some good pointers.Hence we would like to get rid of the
%S
specifiers. The only issue is that if we convert the wide strings into usual strings with our existing functions then we must not forget tofree
them, etc. Also it would be nicer not to allocate when printing GC debug messages.This PR defines a function
win_utf8_string_of_utf16
inwin32.c
which 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_arg
which were being "leaked" (just before the program ends).Together with this change, we also add a call to
SetConsoleOutputCP
in 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