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
* locale_t to zero in that case.
* The "C" and "POSIX" locales are not actually handled by libc, so return
* NULL.
*
* Ensure that no path leaks a locale_t.
*/
static void
make_libc_collator(const char *collate, const char *ctype,
pg_locale_t result)
static locale_t
make_libc_collator(const char *collate, const char *ctype)
{
locale_t loc = 0;
@ -1343,7 +1344,11 @@ make_libc_collator(const char *collate, const char *ctype,
errno = 0;
loc = newlocale(LC_CTYPE_MASK, ctype, loc1);
if (!loc)
{
if (loc1)
freelocale(loc1);
report_newlocale_failure(ctype);
}
}
else
loc = loc1;
@ -1360,60 +1365,78 @@ make_libc_collator(const char *collate, const char *ctype,
#endif
}
result->info.lt = loc;
return loc;
}
void
make_icu_collator(const char *iculocstr,
const char *icurules,
struct pg_locale_struct *resultp)
{
/*
* Create a UCollator with the given locale string and rules.
*
* Ensure that no path leaks a UCollator.
*/
#ifdef USE_ICU
UCollator *collator;
collator = pg_ucol_open(iculocstr);
/*
* 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)
static UCollator *
make_icu_collator(const char *iculocstr, const char *icurules)
{
if (!icurules)
{
const UChar *default_rules;
UChar *agg_rules;
/* simple case without rules */
return pg_ucol_open(iculocstr);
}
else
{
UCollator *collator_std_rules;
UCollator *collator_all_rules;
const UChar *std_rules;
UChar *my_rules;
UErrorCode status;
UChar *all_rules;
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));
agg_rules = palloc_array(UChar, u_strlen(default_rules) + u_strlen(my_rules) + 1);
u_strcpy(agg_rules, default_rules);
u_strcat(agg_rules, my_rules);
collator_std_rules = pg_ucol_open(iculocstr);
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;
collator = ucol_openRules(agg_rules, u_strlen(agg_rules),
UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH, NULL, &status);
collator_all_rules = ucol_openRules(all_rules, u_strlen(all_rules),
UCOL_DEFAULT, UCOL_DEFAULT_STRENGTH,
NULL, &status);
if (U_FAILURE(status))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not open collator for locale \"%s\" with rules \"%s\": %s",
iculocstr, icurules, u_errorName(status))));
}
}
/* We will leak this string if the caller errors later :-( */
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 */
return collator_all_rules;
}
}
#endif /* not USE_ICU */
/*
* Initialize default_locale with database locale settings.
@ -1424,7 +1447,6 @@ init_database_collation(void)
HeapTuple tup;
Form_pg_database dbform;
Datum datum;
bool isnull;
/* Fetch our pg_database row normally, via syscache */
tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
@ -1449,8 +1471,10 @@ init_database_collation(void)
}
else if (dbform->datlocprovider == COLLPROVIDER_ICU)
{
#ifdef USE_ICU
char *datlocale;
char *icurules;
bool isnull;
datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datlocale);
datlocale = TextDatumGetCString(datum);
@ -1464,15 +1488,20 @@ init_database_collation(void)
else
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 *datctype;
Assert(dbform->datlocprovider == COLLPROVIDER_LIBC);
datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datcollate);
datcollate = TextDatumGetCString(datum);
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) ||
(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;
@ -1557,25 +1590,9 @@ pg_newlocale_from_collation(Oid collid)
result.info.builtin.locale = MemoryContextStrdup(TopMemoryContext,
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)
{
#ifdef USE_ICU
const char *iculocstr;
const char *icurules;
@ -1591,8 +1608,35 @@ pg_newlocale_from_collation(Oid collid)
else
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,
&isnull);
@ -2500,6 +2544,8 @@ builtin_validate_locale(int encoding, const char *locale)
/*
* Wrapper around ucol_open() to handle API differences for older ICU
* versions.
*
* Ensure that no path leaks a UCollator.
*/
static UCollator *
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;
extern void make_icu_collator(const char *iculocstr,
const char *icurules,
struct pg_locale_struct *resultp);
extern void init_database_collation(void);
extern pg_locale_t pg_newlocale_from_collation(Oid collid);