mirror of
https://github.com/json-c/json-c.git
synced 2026-11-05 21:20:16 +08:00
Fix memory leak in json_tokener_parse_ex due to duplocale
json-c uses duplocale+newlocale+uselocale() to temporarily switch LC_NUMERIC to "C" during JSON number parsing. On AIX, every call to json_tokener_parse_ex permanently leaks memory. This was confirmed with dbx malloc allocation. 0x20008af8 24 0 YORKTOWN 0x20008e18 5128 0 YORKTOWN 0x2000a458 776 0 YORKTOWN 0x2000a768 776 0 YORKTOWN 0x2000aa78 776 0 YORKTOWN 0x2000ad88 776 0 YORKTOWN 0x2000b098 776 0 YORKTOWN 0x2000b6b8 776 0 YORKTOWN dbx malloc allocation showing new allocations surviving after each parse call with ~776 bytes left after invocations. The root cause is that on AIX, locale category objects allocated by duplocale() are not released back to the process heap, even when freelocale() is called correctly. This causes unbounded heap growth in any long-running process that parses JSON repeatedly. The fix it to replace the per-call duplocale+newlocale+reelocale() with a single file-scope static locale_t created once on first call using newlocale(LC_NUMERIC_MASK, "C", (locale_t)0) and reused for the lifetime of the library. This reduces newlocale calls on all platforms. __attribute__((destructor)) function json_tokener_locale_fini() frees the static locale when the library is unloaded via dlclose() or process exit.
This commit is contained in:
1 parent
9e99c1cfe7
commit
fd92d58f2d
1 file changed
+34
-27
+34
-27
@@ -314,6 +314,15 @@ struct json_object *json_tokener_parse_verbose(const char *str, enum json_tokene
|
|||||||
|
|
||||||
/* End optimization macro defs */
|
/* End optimization macro defs */
|
||||||
|
|
||||||
|
#ifdef HAVE_USELOCALE
|
||||||
|
/* One-time C-numeric locale, created on first call and reused forever.
|
||||||
|
* Avoids per-call duplocale() overhead and the memory leak seen on AIX
|
||||||
|
* where freelocale() does not release all locale category memory.
|
||||||
|
* Freed on library unload by json_tokener_locale_fini().
|
||||||
|
*/
|
||||||
|
static locale_t c_numeric_locale = (locale_t)0;
|
||||||
|
#endif /* HAVE_USELOCALE */
|
||||||
|
|
||||||
struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *str, int len)
|
struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *str, int len)
|
||||||
{
|
{
|
||||||
struct json_object *obj = NULL;
|
struct json_object *obj = NULL;
|
||||||
@@ -323,7 +332,6 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
|
|||||||
|
|
||||||
#ifdef HAVE_USELOCALE
|
#ifdef HAVE_USELOCALE
|
||||||
locale_t oldlocale = uselocale(NULL);
|
locale_t oldlocale = uselocale(NULL);
|
||||||
locale_t newloc;
|
|
||||||
#elif defined(HAVE_SETLOCALE)
|
#elif defined(HAVE_SETLOCALE)
|
||||||
char *oldlocale = NULL;
|
char *oldlocale = NULL;
|
||||||
#endif
|
#endif
|
||||||
@@ -345,33 +353,16 @@ struct json_object *json_tokener_parse_ex(struct json_tokener *tok, const char *
|
|||||||
|
|
||||||
#ifdef HAVE_USELOCALE
|
#ifdef HAVE_USELOCALE
|
||||||
{
|
{
|
||||||
#ifdef HAVE_DUPLOCALE
|
if (c_numeric_locale == (locale_t)0)
|
||||||
locale_t duploc = duplocale(oldlocale);
|
|
||||||
if (duploc == NULL && errno == ENOMEM)
|
|
||||||
{
|
{
|
||||||
tok->err = json_tokener_error_memory;
|
c_numeric_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
|
||||||
return NULL;
|
if (c_numeric_locale == (locale_t)0)
|
||||||
|
{
|
||||||
|
tok->err = json_tokener_error_memory;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
newloc = newlocale(LC_NUMERIC_MASK, "C", duploc);
|
uselocale(c_numeric_locale);
|
||||||
#else
|
|
||||||
newloc = newlocale(LC_NUMERIC_MASK, "C", oldlocale);
|
|
||||||
#endif
|
|
||||||
if (newloc == NULL)
|
|
||||||
{
|
|
||||||
tok->err = json_tokener_error_memory;
|
|
||||||
#ifdef HAVE_DUPLOCALE
|
|
||||||
freelocale(duploc);
|
|
||||||
#endif
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#ifdef NEWLOCALE_NEEDS_FREELOCALE
|
|
||||||
#ifdef HAVE_DUPLOCALE
|
|
||||||
// Older versions of FreeBSD (<12.4) don't free the locale
|
|
||||||
// passed to newlocale(), so do it here
|
|
||||||
freelocale(duploc);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
uselocale(newloc);
|
|
||||||
}
|
}
|
||||||
#elif defined(HAVE_SETLOCALE)
|
#elif defined(HAVE_SETLOCALE)
|
||||||
{
|
{
|
||||||
@@ -1409,7 +1400,6 @@ out:
|
|||||||
|
|
||||||
#ifdef HAVE_USELOCALE
|
#ifdef HAVE_USELOCALE
|
||||||
uselocale(oldlocale);
|
uselocale(oldlocale);
|
||||||
freelocale(newloc);
|
|
||||||
#elif defined(HAVE_SETLOCALE)
|
#elif defined(HAVE_SETLOCALE)
|
||||||
setlocale(LC_NUMERIC, oldlocale);
|
setlocale(LC_NUMERIC, oldlocale);
|
||||||
free(oldlocale);
|
free(oldlocale);
|
||||||
@@ -1476,3 +1466,20 @@ static int json_tokener_parse_double(const char *buf, int len, double *retval)
|
|||||||
return 0; // It worked
|
return 0; // It worked
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_USELOCALE
|
||||||
|
/* Release the static C-numeric locale when the library is unloaded.
|
||||||
|
* This keeps Valgrind clean.
|
||||||
|
*/
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
__attribute__((destructor))
|
||||||
|
static void json_tokener_locale_fini(void)
|
||||||
|
{
|
||||||
|
if (c_numeric_locale != (locale_t)0)
|
||||||
|
{
|
||||||
|
freelocale(c_numeric_locale);
|
||||||
|
c_numeric_locale = (locale_t)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* __GNUC__ || __clang__ */
|
||||||
|
#endif /* HAVE_USELOCALE */
|
||||||
Reference in new issue
Block a user