Skip to content

Commit 7d8a1bb

Browse files
committed
BUG: NrrdIO parse floats locale-independently via strtod_l
All NRRD floating-point parsing funnels through airSingleSscanf(), which handed decimal strings to sscanf() -- honoring the process/thread LC_NUMERIC locale. In a decimal-comma locale (e.g. de_DE.UTF-8), "0.878906" parsed as 0, corrupting NRRD spacings, space directions, origins, and ASCII-encoded pixel values. Parse the single floating-point value at that choke point with strtod_l() against a cached "C" locale (POSIX newlocale + pthread_once; Windows _create_locale + atomic-CAS publish; sscanf() fallback where the *_l API is unavailable). Behavior is unchanged where the feature is absent. This is the ITK-vendored counterpart of the upstream teem branch locale-independent-numeric-parse.
1 parent b62af18 commit 7d8a1bb

1 file changed

Lines changed: 75 additions & 3 deletions

File tree

Modules/ThirdParty/NrrdIO/src/NrrdIO/parseAir.c

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,60 @@
2323
3. This notice may not be removed or altered from any source distribution.
2424
*/
2525

26+
/* Enable the POSIX-2008 thread-safe locale API (newlocale/strtod_l) where
27+
available; must precede the first system-header include (NrrdIO.h pulls in
28+
<stdlib.h>). NRRDIO_HAS_STRTOD_L selects a locale-independent numeric parse
29+
in airSingleSscanf(); platforms lacking it keep the historical sscanf(). */
30+
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) \
31+
|| defined(__OpenBSD__) || defined(__DragonFly__)
32+
# include <xlocale.h>
33+
# include <pthread.h>
34+
# define NRRDIO_HAS_STRTOD_L 1
35+
#elif defined(__GLIBC__)
36+
# ifndef _GNU_SOURCE
37+
# define _GNU_SOURCE 1
38+
# endif
39+
# include <locale.h>
40+
# include <pthread.h>
41+
# define NRRDIO_HAS_STRTOD_L 1
42+
#elif defined(_MSC_VER)
43+
# include <locale.h>
44+
# include <intrin.h>
45+
# define NRRDIO_HAS_STRTOD_L_WIN 1
46+
#endif
47+
2648
#include "NrrdIO.h"
2749

50+
#if defined(NRRDIO_HAS_STRTOD_L)
51+
/* Cached "C" locale for locale-independent strtod_l; created once. */
52+
static locale_t _nrrdCLocale = (locale_t)0;
53+
static pthread_once_t _nrrdCLocaleOnce = PTHREAD_ONCE_INIT;
54+
static void
55+
_nrrdCLocaleInit(void) {
56+
_nrrdCLocale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
57+
}
58+
static locale_t
59+
_nrrdGetCLocale(void) {
60+
pthread_once(&_nrrdCLocaleOnce, _nrrdCLocaleInit);
61+
return _nrrdCLocale;
62+
}
63+
#elif defined(NRRDIO_HAS_STRTOD_L_WIN)
64+
/* Cached "C" locale published exactly once via an atomic pointer CAS, so
65+
concurrent first use is race-free: the losing thread frees its duplicate. */
66+
static void *volatile _nrrdCLocale = NULL;
67+
static _locale_t
68+
_nrrdGetCLocale(void) {
69+
_locale_t loc = (_locale_t)_nrrdCLocale;
70+
if (!loc) {
71+
_locale_t created = _create_locale(LC_NUMERIC, "C");
72+
void *prev = _InterlockedCompareExchangePointer(&_nrrdCLocale, created, NULL);
73+
if (prev) { _free_locale(created); loc = (_locale_t)prev; }
74+
else { loc = created; }
75+
}
76+
return loc;
77+
}
78+
#endif
79+
2880
/* clang-format off */
2981
static const char *
3082
boolStr[] = {
@@ -105,12 +157,32 @@ airSingleSscanf(const char *str, const char *fmt, void *ptr) {
105157
} else if (strstr(tmp, "inf")) {
106158
val = (double)AIR_POS_INF;
107159
} else {
108-
/* nothing special matched; pass it off to sscanf() */
109-
/* (save setlocale here) */
160+
/* nothing special matched; parse one floating-point value with a
161+
locale-independent "C" parse so NRRD's '.' decimal separator is
162+
honored regardless of the process/thread LC_NUMERIC setting. */
163+
#if defined(NRRDIO_HAS_STRTOD_L) || defined(NRRDIO_HAS_STRTOD_L_WIN)
164+
char *endptr = NULL;
165+
double dval;
166+
# if defined(NRRDIO_HAS_STRTOD_L)
167+
locale_t cloc = _nrrdGetCLocale();
168+
dval = cloc ? strtod_l(str, &endptr, cloc) : strtod(str, &endptr);
169+
# else
170+
_locale_t cloc = _nrrdGetCLocale();
171+
dval = cloc ? _strtod_l(str, &endptr, cloc) : strtod(str, &endptr);
172+
# endif
173+
ret = (endptr != str) ? 1 : 0;
174+
if (ret) {
175+
if (fmt[1] == 'l') { *((double *)(ptr)) = dval; }
176+
else { *((float *)(ptr)) = AIR_FLOAT(dval); }
177+
}
178+
free(tmp);
179+
return ret;
180+
#else
181+
/* fallback: historical locale-dependent behavior, unchanged */
110182
ret = sscanf(str, fmt, ptr);
111-
/* (return setlocale here) */
112183
free(tmp);
113184
return ret;
185+
#endif
114186
}
115187
/* else we matched "nan", "-inf", or "inf"; now set val accordingly */
116188
if (fmt[1] == 'l') {

0 commit comments

Comments
 (0)