This is an archive of the discontinued LLVM Phabricator instance.

[libcxx] [Windows] Store the lconv struct returned from localeconv in locale_t
ClosedPublic

Authored by mstorsjo on Oct 28 2019, 4:33 AM.

Details

Summary

This fixes using non-default locales, which currently can crash when e.g. formatting numbers.

Within the localeconv_l function, the per-thread locale is temporarily changed with __libcpp_locale_guard, then localeconv() is called, returning an lconv * struct pointer.

When localeconv_l returns, the __libcpp_locale_guard dtor restores the per-thread locale back to the original. This invalidates the contents of the earlier returned lconv struct, and all C strings that are pointed to within it are also invalidated.

Thus, to have an actually working localeconv_l function, the function needs to allocate some sort of storage for the returned contents, that stays valid for as long as the caller needs to use the returned struct.

Extend the libcxx/win32 specific locale_t class with storage for a deep copy of a lconv struct, and change localeconv_l to take a reference to the locale_t, to allow it to store the returned lconv struct there.

This works fine for libcxx itself, but wouldn't necessarily be right for a caller that uses libcxx's localeconv_l function.

Diff Detail

Event Timeline

mstorsjo created this revision.Oct 28 2019, 4:33 AM
rnk added a comment.Oct 29 2019, 3:12 PM

Exciting. =/ So, successive calls to localeconv_l would invalidate the result from the last call. I could live with that if it was possible to make it private to libcxx.

In D69505#1726163, @rnk wrote:

Exciting. =/ So, successive calls to localeconv_l would invalidate the result from the last call.

It's even worse than that. If you call localeconv_l with a locale other than the currently set thread specific locale, __libcpp_locale_guard will temporarily set the one we want to inspect, then receive the lconv * return pointer, and then reset the thread specific locale to what it was before the function was called. When the thread specific locale is reset on return, the lconv * pointer we're returning is invalidated.

So currently, localeconv_l never works, unless it's used on the currently set locale.

I could live with that if it was possible to make it private to libcxx.

Any suggestions on how to achieve that? Everything in include/locale, include/__locale and include/support/win32/locale_win32.h ends up included by callers. This current patch changes the ABI of the locale_t class on windows, but is that part of the API/ABI that users of libcxx are expected to use, or is it just a helper that accidentally ends up visible? (The locale_t class ends up defined without any enclosing namespace whatsoever, at the moment.)

One way could be to add some wrapper around the localeconv_l calls in src/locale.cpp. Ideally a wrapper that still returns lconv * for compatibility with everything else, but which can be made to allocate temp storage on the stack for windows. But I struggle to see a neat way of doing it.

One way that does come to mind is something preprocessor based, like this:

#ifdef _WIN32
#define GET_LCONV_PTR(var, loc) \
  lconv_storage lcs##var; \
  { \
    __libcpp_locale_guard __current(loc); \
    lcs.store(localeconv()); \
  } \
  lconv *var = lcs##var.getPtr()
#else
#define GET_LCONV_PTR(var, loc) \
  lconv *var = localeconv_l(loc)
#endif
rnk accepted this revision.Nov 17 2019, 9:13 AM

I don't have any great ideas, honestly. It seems like if we want to make non-default locales work well with libc++ on Windows, we're going to have to do more work to reimplement locales or find some new entry points in ucrtbase.dll that take the locale as a parameter instead of as a global.

In the meantime, this seems fine as stopgap fix.

This revision is now accepted and ready to land.Nov 17 2019, 9:13 AM

Thanks for looking into this again!

Do you have any input/opinion on tests for this (as @mclow.lists requested for D69554 as well)?

FWIW, I looked into tests, and this seems to fix at least 11 failing tests in libc++ on windows.

@EricWF / @mclow.lists - what do you think of it?

FWIW, I looked into tests, and this seems to fix at least 11 failing tests in libc++ on windows.

@EricWF / @mclow.lists - what do you think of it?

Ping @EricWF

ldionne accepted this revision.Jan 27 2020, 2:31 PM

Is it easy to add a test for this behavior? Otherwise, LGTM.

Is it easy to add a test for this behavior? Otherwise, LGTM.

This is already covered by the existing tests - this pach fixes around 10 of the currently failing tests.

@mclow.lists - did you want to dig further into this, or can I go ahead with it?

This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptJan 29 2020, 12:44 PM
mstorsjo added a subscriber: hans.Jan 29 2020, 12:47 PM

@hans - It would be nice to have this i 10.x, after letting it sit in master for a few days.

hans added a comment.Feb 4 2020, 2:44 AM

@hans - It would be nice to have this i 10.x, after letting it sit in master for a few days.

Cherry-picked in ca6b341bd5d7159d9e398eef1a787b649c5bc888.