Tighten up make_libc_collator() and make_icu_collator().

Ensure that error paths within these functions do not leak a collator,
and return the result rather than using an out parameter. (Error paths
in the caller may still result in a leaked collator, which will be
addressed separately.)

In make_libc_collator(), if the first newlocale() succeeds and the
second one fails, close the first locale_t object.

The function make_icu_collator() doesn't have any external callers, so
change it to be static.

Discussion: https://postgr.es/m/54d20e812bd6c3e44c10eddcd757ec494ebf1803.camel@j-davis.com
This commit is contained in:
Jeff Davis 2024-09-24 12:01:45 -07:00
parent 59f0eea7b0
commit ceeaaed87a
2 changed files with 111 additions and 69 deletions

View File

@ -1297,14 +1297,15 @@ report_newlocale_failure(const char *localename)
} }
/* /*
* Initialize the locale_t field. * Create a locale_t with the given collation and ctype.
* *
* The "C" and "POSIX" locales are not actually handled by libc, so set the * The "C" and "POSIX" locales are not actually handled by libc, so return
* locale_t to zero in that case. * NULL.
*
* Ensure that no path leaks a locale_t.
*/ */
static void static locale_t
make_libc_collator(const char *collate, const char *ctype, make_libc_collator(const char *collate, const char *ctype)
pg_locale_t result)
{ {
locale_t loc = 0; locale_t loc = 0;
@ -1343,7 +1344,11 @@ make_libc_collator(const char *collate, const char *ctype,
errno = 0; errno = 0;
loc = newlocale(LC_CTYPE_MASK, ctype, loc1); loc = newlocale(LC_CTYPE_MASK, ctype, loc1);
if (!loc) if (!loc)
{
if (loc1)
freelocale(loc1);
report_newlocale_failure(ctype); report_newlocale_failure(ctype);
}
} }
else else
loc = loc1; loc = loc1;
@ -1360,60 +1365,78 @@ make_libc_collator(const char *collate, const char *ctype,
#endif #endif
} }
result->info.lt = loc; return loc;
} }
void /*
make_icu_collator(const char *iculocstr, * Create a UCollator with the given locale string and rules.
const char *icurules, *
struct pg_locale_struct *resultp) * Ensure that no path leaks a UCollator.
{ */
#ifdef USE_ICU #ifdef USE_ICU
UCollator *collator; static UCollator *
make_icu_collator(const char *iculocstr, const char *icurules)
collator = pg_ucol_open(iculocstr); {
if (!icurules)
/*
* If rules are specified, we extract the rules of the standard collation,
* add our own rules, and make a new collator with the combined rules.
*/
if (icurules)
{ {
const UChar *default_rules; /* simple case without rules */
UChar *agg_rules; return pg_ucol_open(iculocstr);
}
else
{
UCollator *collator_std_rules;
UCollator *collator_all_rules;
const UChar *std_rules;
UChar *my_rules; UChar *my_rules;
UErrorCode status; UChar *all_rules;
int32_t length; int32_t length;
int32_t total;
UErrorCode status;
default_rules = ucol_getRules(collator, &length); /*
* If rules are specified, we extract the rules of the standard
* collation, add our own rules, and make a new collator with the
* combined rules.
*/
icu_to_uchar(&my_rules, icurules, strlen(icurules)); icu_to_uchar(&my_rules, icurules, strlen(icurules));
agg_rules = palloc_array(UChar, u_strlen(default_rules) + u_strlen(my_rules) + 1); collator_std_rules = pg_ucol_open(iculocstr);
u_strcpy(agg_rules, default_rules);
u_strcat(agg_rules, my_rules);
ucol_close(collator); std_rules = ucol_getRules(collator_std_rules, &length);
total = u_strlen(std_rules) + u_strlen(my_rules) + 1;
/* avoid leaking collator on OOM */
all_rules = palloc_extended(sizeof(UChar) * total, MCXT_ALLOC_NO_OOM);
if (!all_rules)
{
ucol_close(collator_std_rules);
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
u_strcpy(all_rules, std_rules);
u_strcat(all_rules, my_rules);
ucol_close(collator_std_rules);
status = U_ZERO_ERROR; status = U_ZERO_ERROR;
collator = ucol_openRules(agg_rules, u_strlen(agg_rules), collator_all_rules = ucol_openRules(all_rules, u_strlen(all_rules),
UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH, NULL, &status); UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH,
NULL, &status);
if (U_FAILURE(status)) if (U_FAILURE(status))
{
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s", errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s",
iculocstr, icurules, u_errorName(status)))); iculocstr, icurules, u_errorName(status))));
} }
/* We will leak this string if the caller errors later :-( */ return collator_all_rules;
resultp->info.icu.locale = MemoryContextStrdup(TopMemoryContext, iculocstr); }
resultp->info.icu.ucol = collator;
#else /* not USE_ICU */
/* could get here if a collation was created by a build with ICU */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ICU is not supported in this build")));
#endif /* not USE_ICU */
} }
#endif /* not USE_ICU */
/* /*
* Initialize default_locale with database locale settings. * Initialize default_locale with database locale settings.
@ -1424,7 +1447,6 @@ init_database_collation(void)
HeapTuple tup; HeapTuple tup;
Form_pg_database dbform; Form_pg_database dbform;
Datum datum; Datum datum;
bool isnull;
/* Fetch our pg_database row normally, via syscache */ /* Fetch our pg_database row normally, via syscache */
tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
@ -1449,8 +1471,10 @@ init_database_collation(void)
} }
else if (dbform->datlocprovider == COLLPROVIDER_ICU) else if (dbform->datlocprovider == COLLPROVIDER_ICU)
{ {
#ifdef USE_ICU
char *datlocale; char *datlocale;
char *icurules; char *icurules;
bool isnull;
datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datlocale); datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datlocale);
datlocale = TextDatumGetCString(datum); datlocale = TextDatumGetCString(datum);
@ -1464,15 +1488,20 @@ init_database_collation(void)
else else
icurules = NULL; icurules = NULL;
make_icu_collator(datlocale, icurules, &default_locale); default_locale.info.icu.locale = MemoryContextStrdup(TopMemoryContext, datlocale);
default_locale.info.icu.ucol = make_icu_collator(datlocale, icurules);
#else
/* could get here if a collation was created by a build with ICU */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ICU is not supported in this build")));
#endif
} }
else else if (dbform->datlocprovider == COLLPROVIDER_LIBC)
{ {
const char *datcollate; const char *datcollate;
const char *datctype; const char *datctype;
Assert(dbform->datlocprovider == COLLPROVIDER_LIBC);
datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datcollate); datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datcollate);
datcollate = TextDatumGetCString(datum); datcollate = TextDatumGetCString(datum);
datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype); datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype);
@ -1483,8 +1512,12 @@ init_database_collation(void)
default_locale.ctype_is_c = (strcmp(datctype, "C") == 0) || default_locale.ctype_is_c = (strcmp(datctype, "C") == 0) ||
(strcmp(datctype, "POSIX") == 0); (strcmp(datctype, "POSIX") == 0);
make_libc_collator(datcollate, datctype, &default_locale); default_locale.info.lt = make_libc_collator(datcollate, datctype);
} }
else
/* shouldn't happen */
PGLOCALE_SUPPORT_ERROR(dbform->datlocprovider);
default_locale.provider = dbform->datlocprovider; default_locale.provider = dbform->datlocprovider;
@ -1557,25 +1590,9 @@ pg_newlocale_from_collation(Oid collid)
result.info.builtin.locale = MemoryContextStrdup(TopMemoryContext, result.info.builtin.locale = MemoryContextStrdup(TopMemoryContext,
locstr); locstr);
} }
else if (collform->collprovider == COLLPROVIDER_LIBC)
{
const char *collcollate;
const char *collctype;
datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate);
collcollate = TextDatumGetCString(datum);
datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype);
collctype = TextDatumGetCString(datum);
result.collate_is_c = (strcmp(collcollate, "C") == 0) ||
(strcmp(collcollate, "POSIX") == 0);
result.ctype_is_c = (strcmp(collctype, "C") == 0) ||
(strcmp(collctype, "POSIX") == 0);
make_libc_collator(collcollate, collctype, &result);
}
else if (collform->collprovider == COLLPROVIDER_ICU) else if (collform->collprovider == COLLPROVIDER_ICU)
{ {
#ifdef USE_ICU
const char *iculocstr; const char *iculocstr;
const char *icurules; const char *icurules;
@ -1591,8 +1608,35 @@ pg_newlocale_from_collation(Oid collid)
else else
icurules = NULL; icurules = NULL;
make_icu_collator(iculocstr, icurules, &result); result.info.icu.locale = MemoryContextStrdup(TopMemoryContext, iculocstr);
result.info.icu.ucol = make_icu_collator(iculocstr, icurules);
#else
/* could get here if a collation was created by a build with ICU */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ICU is not supported in this build")));
#endif
} }
else if (collform->collprovider == COLLPROVIDER_LIBC)
{
const char *collcollate;
const char *collctype;
datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collcollate);
collcollate = TextDatumGetCString(datum);
datum = SysCacheGetAttrNotNull(COLLOID, tp, Anum_pg_collation_collctype);
collctype = TextDatumGetCString(datum);
result.collate_is_c = (strcmp(collcollate, "C") == 0) ||
(strcmp(collcollate, "POSIX") == 0);
result.ctype_is_c = (strcmp(collctype, "C") == 0) ||
(strcmp(collctype, "POSIX") == 0);
result.info.lt = make_libc_collator(collcollate, collctype);
}
else
/* shouldn't happen */
PGLOCALE_SUPPORT_ERROR(collform->collprovider);
datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion, datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collversion,
&isnull); &isnull);
@ -2500,6 +2544,8 @@ builtin_validate_locale(int encoding, const char *locale)
/* /*
* Wrapper around ucol_open() to handle API differences for older ICU * Wrapper around ucol_open() to handle API differences for older ICU
* versions. * versions.
*
* Ensure that no path leaks a UCollator.
*/ */
static UCollator * static UCollator *
pg_ucol_open(const char *loc_str) pg_ucol_open(const char *loc_str)

View File

@ -104,10 +104,6 @@ struct pg_locale_struct
typedef struct pg_locale_struct *pg_locale_t; typedef struct pg_locale_struct *pg_locale_t;
extern void make_icu_collator(const char *iculocstr,
const char *icurules,
struct pg_locale_struct *resultp);
extern void init_database_collation(void); extern void init_database_collation(void);
extern pg_locale_t pg_newlocale_from_collation(Oid collid); extern pg_locale_t pg_newlocale_from_collation(Oid collid);