From e4269202c99ec42d973bb3fffe4521eee33acc25 Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Fri, 20 Sep 2024 10:41:00 +0200 Subject: [PATCH] keysyms: Fix off-by-one XKB_KEYSYM_NAME_MAX_SIZE The constant did not account for the terminating `NULL` byte and this was sadly not caught by the tests. Fixed the invalid value, the corresponding script and the tests. --- .../api/+fix-keysym-name-max-size.bugfix.md | 1 + .../tools/+fix-keysym-name-max-size.bugfix.md | 1 + scripts/update-headers.py | 29 ++++++++++++++----- src/keysym.h | 8 +++-- src/keysym.h.jinja | 6 +++- test/keysym.c | 13 +++++++-- 6 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 changes/api/+fix-keysym-name-max-size.bugfix.md create mode 100644 changes/tools/+fix-keysym-name-max-size.bugfix.md diff --git a/changes/api/+fix-keysym-name-max-size.bugfix.md b/changes/api/+fix-keysym-name-max-size.bugfix.md new file mode 100644 index 000000000..76789d0c6 --- /dev/null +++ b/changes/api/+fix-keysym-name-max-size.bugfix.md @@ -0,0 +1 @@ +Fixed `xkb_keymap_get_as_string` truncating the *4* longest keysyms names, such as `Greek_upsilonaccentdieresis`. diff --git a/changes/tools/+fix-keysym-name-max-size.bugfix.md b/changes/tools/+fix-keysym-name-max-size.bugfix.md new file mode 100644 index 000000000..eea106c35 --- /dev/null +++ b/changes/tools/+fix-keysym-name-max-size.bugfix.md @@ -0,0 +1 @@ +Fixed various tools truncating the *4* longest keysyms names, such as `Greek_upsilonaccentdieresis`. diff --git a/scripts/update-headers.py b/scripts/update-headers.py index e087759db..e1c1ac62a 100755 --- a/scripts/update-headers.py +++ b/scripts/update-headers.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 import argparse -from collections import defaultdict -from pathlib import Path +import itertools import re import sys +from collections import defaultdict +from pathlib import Path from typing import Any, TypeAlias import jinja2 @@ -14,7 +15,7 @@ ) MAX_AMBIGUOUS_NAMES = 3 -KeysymsBounds: TypeAlias = dict[str, int] +KeysymsBounds: TypeAlias = dict[str, int | str] KeysymsCaseFoldedNames: TypeAlias = dict[str, list[str]] @@ -40,6 +41,19 @@ def load_keysyms(path: Path) -> tuple[KeysymsBounds, KeysymsCaseFoldedNames]: if value not in canonical_names: canonical_names[value] = name + XKB_KEYSYM_LONGEST_CANONICAL_NAME = max( + max(canonical_names.values(), key=len), + max_unicode_name, + max_keysym_name, + key=len, + ) + XKB_KEYSYM_LONGEST_NAME = max( + max(itertools.chain.from_iterable(casefolded_names.values()), key=len), + max_unicode_name, + max_keysym_name, + key=len, + ) + # Keep only ambiguous case-insensitive names and sort them for name in tuple(casefolded_names.keys()): count = len(casefolded_names[name]) @@ -59,11 +73,10 @@ def load_keysyms(path: Path) -> tuple[KeysymsBounds, KeysymsCaseFoldedNames]: "XKB_KEYSYM_MIN_EXPLICIT": keysym_min, "XKB_KEYSYM_MAX_EXPLICIT": keysym_max, "XKB_KEYSYM_COUNT_EXPLICIT": len(canonical_names), - "XKB_KEYSYM_NAME_MAX_SIZE": max( - max(len(name) for name in canonical_names.values()), - len(max_unicode_name), - len(max_keysym_name), - ), + # Extra byte for terminating NULL + "XKB_KEYSYM_NAME_MAX_SIZE": len(XKB_KEYSYM_LONGEST_CANONICAL_NAME) + 1, + "XKB_KEYSYM_LONGEST_CANONICAL_NAME": XKB_KEYSYM_LONGEST_CANONICAL_NAME, + "XKB_KEYSYM_LONGEST_NAME": XKB_KEYSYM_LONGEST_NAME, }, casefolded_names, ) diff --git a/src/keysym.h b/src/keysym.h index 80efc31ef..76b3f89bf 100644 --- a/src/keysym.h +++ b/src/keysym.h @@ -78,8 +78,12 @@ #define XKB_KEYSYM_UNICODE_MAX 0x0110ffff /** Unicode version used for case mappings */ #define XKB_KEYSYM_UNICODE_VERSION { 15, 1, 0, 0 } -/** Maximum keysym name length */ -#define XKB_KEYSYM_NAME_MAX_SIZE 27 +/** Maximum keysym canonical name length, plus terminating NULL byte */ +#define XKB_KEYSYM_NAME_MAX_SIZE 28 +/** Longest keysym canonical name */ +#define XKB_KEYSYM_LONGEST_CANONICAL_NAME ISO_Discontinuous_Underline +/** Longest keysym name */ +#define XKB_KEYSYM_LONGEST_NAME ISO_Discontinuous_Underline /** Maximum bytes to encode the Unicode representation of a keysym in UTF-8: * 4 bytes + NULL-terminating byte */ #define XKB_KEYSYM_UTF8_MAX_SIZE 5 diff --git a/src/keysym.h.jinja b/src/keysym.h.jinja index e65c750d5..190ab78af 100644 --- a/src/keysym.h.jinja +++ b/src/keysym.h.jinja @@ -78,8 +78,12 @@ #define XKB_KEYSYM_UNICODE_MAX 0x0110ffff /** Unicode version used for case mappings */ #define XKB_KEYSYM_UNICODE_VERSION { 15, 1, 0, 0 } -/** Maximum keysym name length */ +/** Maximum keysym canonical name length, plus terminating NULL byte */ #define XKB_KEYSYM_NAME_MAX_SIZE {{ XKB_KEYSYM_NAME_MAX_SIZE }} +/** Longest keysym canonical name */ +#define XKB_KEYSYM_LONGEST_CANONICAL_NAME {{ XKB_KEYSYM_LONGEST_CANONICAL_NAME }} +/** Longest keysym name */ +#define XKB_KEYSYM_LONGEST_NAME {{ XKB_KEYSYM_LONGEST_NAME }} /** Maximum bytes to encode the Unicode representation of a keysym in UTF-8: * 4 bytes + NULL-terminating byte */ #define XKB_KEYSYM_UTF8_MAX_SIZE 5 diff --git a/test/keysym.c b/test/keysym.c index 254eb069b..190c9c5d3 100644 --- a/test/keysym.c +++ b/test/keysym.c @@ -474,10 +474,10 @@ main(void) char utf8[7]; int needed = xkb_keysym_to_utf8(ks, utf8, sizeof(utf8)); assert(0 <= needed && needed <= XKB_KEYSYM_UTF8_MAX_SIZE); - /* Check maximum name length */ + /* Check maximum name length (`needed` does not include the ending NULL) */ char name[XKB_KEYSYM_NAME_MAX_SIZE]; needed = xkb_keysym_iterator_get_name(iter, name, sizeof(name)); - assert(0 < needed && (size_t)needed <= sizeof(name)); + assert(0 < needed && (size_t)needed <= sizeof(name) - 1); /* Test modifier keysyms */ bool expected = test_modifier(ks); bool got = xkb_keysym_is_modifier(ks); @@ -519,6 +519,12 @@ main(void) assert(test_string("thorn", 0x00fe)); assert(test_string(" thorn", XKB_KEY_NoSymbol)); assert(test_string("thorn ", XKB_KEY_NoSymbol)); +#define LONGEST_NAME STRINGIFY2(XKB_KEYSYM_LONGEST_NAME) +#define XKB_KEY_LONGEST_NAME CONCAT2(XKB_KEY_, XKB_KEYSYM_LONGEST_NAME) + assert(test_string(LONGEST_NAME, XKB_KEY_LONGEST_NAME)); +#define LONGEST_CANONICAL_NAME STRINGIFY2(XKB_KEYSYM_LONGEST_CANONICAL_NAME) +#define XKB_KEY_LONGEST_CANONICAL_NAME CONCAT2(XKB_KEY_, XKB_KEYSYM_LONGEST_CANONICAL_NAME) + assert(test_string(LONGEST_CANONICAL_NAME, XKB_KEY_LONGEST_CANONICAL_NAME)); /* Decimal keysyms are not supported (digits are special cases) */ assert(test_string("-1", XKB_KEY_NoSymbol)); @@ -586,6 +592,9 @@ main(void) assert(test_keysym(0x0, "NoSymbol")); assert(test_keysym(0x1008FE20, "XF86Ungrab")); assert(test_keysym(XKB_KEYSYM_UNICODE_OFFSET, "0x01000000")); + /* Longest names */ + assert(test_keysym(XKB_KEY_LONGEST_NAME, LONGEST_NAME)); + assert(test_keysym(XKB_KEY_LONGEST_CANONICAL_NAME, LONGEST_CANONICAL_NAME)); /* Canonical names */ assert(test_keysym(XKB_KEY_Henkan, "Henkan_Mode")); assert(test_keysym(XKB_KEY_ISO_Group_Shift, "Mode_switch"));