Ticket #2 (closed defect: fixed)

Opened 2 years ago

Last modified 2 years ago

Crash on attempt to write float value to stream

Reported by: dmc Owned by: ognian
Priority: blocker Milestone: r3-crystax-3
Component: ndk Version: r3-crystax-2
Severity: Keywords:
Cc: ognian Blocked By:
Blocking:

Description

Example:

#include <cstdio>
#include <sstream>

int main()
{

std::stringstream ostr;
std::printf("Before write\n");
ostr << 1.0;
std::printf("After write\n");
return 0;

}

Expected output:

Before write
After write

Actual output:

Before write

Application crashed when attempting write any float value to C++ stream (it should be any C++ stream, not only stringstream). Probably it is because Bionic does not support C locales, but this is not confirmed and need to be investigated.

Attachments

locale.patch (11.4 KB) - added by dmc 2 years ago.

Change History

comment:1 Changed 2 years ago by ognian

I have located the exact place it crashes - /gcc/gcc-4.4.0/libstdc++-v3/config/locale/generic/c_locale.h:

    char* __old = std::setlocale(LC_NUMERIC, NULL);
    char* __sav = NULL;
    if (__builtin_strcmp(__old, "C"))
      {
	const size_t __len = __builtin_strlen(__old) + 1;
	__sav = new char[__len];
	__builtin_memcpy(__sav, __old, __len);
	std::setlocale(LC_NUMERIC, "C");
      }

in strcmp(), because old is NULL. I could use some advice how to fix it.

comment:2 Changed 2 years ago by ognian

Further investigation shows that the trouble is caused by lack of locale support. The implementation of setlocale() is return hardcoded 0:

http://android.git.kernel.org/?p=platform/bionic.git;a=blob_plain;f=libc/stdlib/locale.c;hb=HEAD

I can think of two workarounds:

  1. Rebuild bionic to return a pointer to "C" string, maybe Google should have implemented it that way. Currently this will bother users of ndk-r3-crystax because it'll require to link statically against the custom bionic.
  2. Modify libstdc++ so it contains
#define setlocale(category, locale) "C"

comment:3 Changed 2 years ago by dmc

I believe the best way is second. The only I want note is about macros. Let avoid them if possible. Let see to the libstdc++ sources - how setlocale used there? Is it always std::setlocale? If yes, let add new inline function in std namespace for that (somewhere in the common used libstdc++ header):

inline char* setlocale (int category, char const *locale)
{
    return "C";
}

comment:4 follow-up: ↓ 5 Changed 2 years ago by dmc

  • Owner changed from dmc to ognian
  • Status changed from new to assigned

comment:5 in reply to: ↑ 4 Changed 2 years ago by ognian

Are you sure returning a local char[] will work well with inline? Is there a chance that the function won't be actually inlined by compiler and return the address of freed data?

I would suggest a safer realization, in case this is not standard behavior:

static const char* const _c_locale_str = "C";

inline const char* setlocale (int category, char const* locale) {
    return _c_locale_str;
}

In libc documentation is stated that returned value of setlocale shouldn't be changed, so I guess we'll be safe with that implementation.

comment:6 Changed 2 years ago by dmc

Yes, I'm sure. "C" actually is not local char[] - it is pointer to the statically defined zero-terminated char array located in data segment. This is how C and C++ works.

comment:7 Changed 2 years ago by dmc

  • Milestone set to r3-crystax-3

comment:8 Changed 2 years ago by dmc

  • Status changed from assigned to closed
  • Resolution set to fixed

After lot of attempts I have not found good solution to redefine setlocale in gcc sources - if I declare setlocale in std namespace - it works only for C++ code, but there are also lot of C sources using setlocale. But I can not declare setlocale as extern "C" function in global namespace - it will conflict with Bionic one. Macros is not solution too - there are lot of places in gcc sources where explicit "#undef setlocale" used to prevent macros.

So I've found all places where setlocale return value used and fixed them like this:

__old = setlocale(.....);
if (__old == NULL || __old[0] == '\0')
  __old = "C";

This do the trick. Now C++ streams works fine with float/double values.

comment:9 Changed 2 years ago by dmc

Will be included to r3-crystax-3 and r4-crystax-1

Changed 2 years ago by dmc

Note: See TracTickets for help on using tickets.