1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

basic/string-util: tweak strverscmp_improved() for some corner cases

So far we had the rule that '' == '', '0_' == '0', but '_' > ''. This means
that the general rule that strings are compared iteratively, and each
segment that compares equal can be dropped and the comparison resumes at
the following characters wasn't true in such cases. Similarly, '0~' < '0',
but after dropping the common segment, '~' > ''.

The special handling of empty strings is dropped, and '_' == '' and
'~' < ''.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2022-05-24 21:20:36 +02:00
parent 0f5a416c81
commit 46083ab321
2 changed files with 37 additions and 9 deletions

View File

@ -124,8 +124,8 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
* (newer) 124-1
*/
if (isempty(a) || isempty(b))
return CMP(strcmp_ptr(a, b), 0);
a = strempty(a);
b = strempty(b);
for (;;) {
const sd_char *aa, *bb;
@ -187,12 +187,6 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
}
if (is_digit(*a) || is_digit(*b)) {
/* Skip leading '0', to make 00123 equivalent to 123. */
while (*a == '0')
a++;
while (*b == '0')
b++;
/* Find the leading numeric segments. One may be an empty string. So,
* numeric segments are always newer than alpha segments. */
for (aa = a; is_digit(*aa); aa++)
@ -200,6 +194,17 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
for (bb = b; is_digit(*bb); bb++)
;
/* Check if one of the strings was empty, but the other not. */
r = CMP(a != aa, b != bb);
if (r != 0)
return r;
/* Skip leading '0', to make 00123 equivalent to 123. */
while (*a == '0')
a++;
while (*b == '0')
b++;
/* To compare numeric segments without parsing their values, first compare the
* lengths of the segments. Eg. 12345 vs 123, longer is newer. */
r = CMP(aa - a, bb - b);

View File

@ -852,8 +852,8 @@ static void test_strverscmp_improved_newer(const char *older, const char *newer)
TEST(strverscmp_improved) {
static const char * const versions[] = {
"",
"~1",
"",
"ab",
"abb",
"abc",
@ -917,6 +917,29 @@ TEST(strverscmp_improved) {
/* invalid characters */
assert_se(strverscmp_improved("123_aa2-67.89", "123aa+2-67.89") == 0);
/* some corner cases */
assert_se(strverscmp_improved("123.", "123") > 0); /* One more version segment */
assert_se(strverscmp_improved("12_3", "123") < 0); /* 12 < 123 */
assert_se(strverscmp_improved("12_3", "12") > 0); /* 3 > '' */
assert_se(strverscmp_improved("12_3", "12.3") > 0); /* 3 > '' */
assert_se(strverscmp_improved("123.0", "123") > 0); /* 0 > '' */
assert_se(strverscmp_improved("123_0", "123") > 0); /* 0 > '' */
assert_se(strverscmp_improved("123..0", "123.0") < 0); /* '' < 0 */
/* empty strings or strings with ignored characters only */
assert_se(strverscmp_improved("", NULL) == 0);
assert_se(strverscmp_improved(NULL, "") == 0);
assert_se(strverscmp_improved("0_", "0") == 0);
assert_se(strverscmp_improved("_0_", "0") == 0);
assert_se(strverscmp_improved("_0", "0") == 0);
assert_se(strverscmp_improved("0", "0___") == 0);
assert_se(strverscmp_improved("", "_") == 0);
assert_se(strverscmp_improved("_", "") == 0);
assert_se(strverscmp_improved("_", "_") == 0);
assert_se(strverscmp_improved("", "~") > 0);
assert_se(strverscmp_improved("~", "") < 0);
assert_se(strverscmp_improved("~", "~") == 0);
/* non-ASCII digits */
(void) setlocale(LC_NUMERIC, "ar_YE.utf8");
assert_se(strverscmp_improved("1٠١٢٣٤٥٦٧٨٩", "1") == 0);