icu: assemble the data blob with --noexecstack; gate continuation comments

ICU emits its data as generated assembly (icudt75l_dat.S). Without
-Wa,--noexecstack the assembler produces an object with no .note.GNU-stack
section, and modern binutils warns:
  ld: warning: icudt75l_dat.o: missing .note.GNU-stack section implies
      executable stack
Harmless alone -- except KDE's ECM links with -Wl,--fatal-warnings, so every
KDE consumer of static ICU fails outright (first hit: plasma-workspace
applets/digital-clock, collect2: error: ld returned 1). Fixed at the source
rather than suppressed downstream, which would have to be repeated per consumer.

Also gates the '#'-inside-a-backslash-continuation trap, which terminates the
continuation and silently drops every remaining argument. It has now bitten
three times, most recently while writing THIS commit: the noexecstack rationale
was first placed between two continued configure flags, which would have
dropped the rest of ICU's configure line. Moved above the invocation.

The check scans the RAW file, not the parsed TOML. In a multi-line basic string
a trailing backslash is itself a TOML line-continuation escape, so the newline
is gone before the value is handed over and the parsed string has no trailing
backslashes at all. The first version scanned the parsed value and silently
found nothing -- caught by a self-test, not by review, which is the same class
of invisible failure the gate exists to prevent.
This commit is contained in:
2026-08-04 20:58:50 +03:00
parent cb7764b6d9
commit f25ccf07c6
2 changed files with 53 additions and 2 deletions
+17 -2
View File
@@ -44,6 +44,21 @@ LDFLAGS="${TARGET_LDFLAGS}"
ln -sf mh-linux "${ICU_SRC}/config/mh-unknown"
# -Wa,--noexecstack is REQUIRED, not hygiene.
#
# ICU emits its data blob as generated assembly (icudt75l_dat.S) and the
# assembler, absent this flag, produces an object with no .note.GNU-stack
# section. Modern binutils then warns:
# ld: warning: icudt75l_dat.o: missing .note.GNU-stack section implies
# executable stack
# which is harmless on its own -- except KDE's ECM links with
# -Wl,--fatal-warnings, so every KDE consumer of static ICU fails outright:
# collect2: error: ld returned 1 exit status
# (first hit: plasma-workspace applets/digital-clock).
#
# Fixed at the source rather than by suppressing the warning downstream: the
# object genuinely should declare a non-executable stack, and silencing it per
# consumer would have to be repeated for every KDE package that links ICU.
"${ICU_SRC}/configure" \
--host="${GNU_TARGET}" \
--prefix=/usr \
@@ -56,8 +71,8 @@ ln -sf mh-linux "${ICU_SRC}/config/mh-unknown"
--enable-static \
--disable-shared \
--with-data-packaging=static \
CFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC" \
CXXFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC" \
CFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC -Wa,--noexecstack" \
CXXFLAGS="-DU_HAVE_CHAR16_T=0 -Wno-deprecated-declarations -fPIC -Wa,--noexecstack" \
icu_cv_host_frag=mh-linux
make -j"${COOKBOOK_MAKE_JOBS}"
+36
View File
@@ -70,6 +70,35 @@ def walk(node, path, findings):
findings.append((path, lineno, advice, shown[:100]))
def find_continuation_comments(raw_text):
r"""Comment lines inside a backslash-continued shell command.
A '#' line terminates a `\`-continuation, so EVERY remaining argument is
silently dropped -- no shell error, no build error, just a command that
quietly ran with fewer flags than it appears to. In kwin this dropped
KWIN_BUILD_SCREENLOCKER, TABBOX, GLOBALSHORTCUTS and RUNNERS, so cmake took
its own defaults and configure later aborted on a REQUIRED package the
recipe had explicitly disabled.
Scans the RAW FILE, not the parsed TOML value. In a TOML multi-line basic
string a trailing backslash is itself a line-continuation escape: TOML
removes the newline before the value is ever handed over, so the parsed
string contains no trailing backslashes and this can only be detected in
the source text. (My first version scanned the parsed value and silently
found nothing -- caught by a self-test, not by review.)
"""
findings = []
prev_cont = False
for lineno, line in enumerate(raw_text.splitlines(), 1):
stripped = line.strip()
if prev_cont and stripped.startswith("#"):
findings.append((lineno, stripped[:88]))
# A line ending in an ODD number of backslashes continues.
trailing = len(line.rstrip()) - len(line.rstrip().rstrip("\\"))
prev_cont = (trailing % 2) == 1
return findings
def main() -> int:
root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")
findings_total = 0
@@ -90,6 +119,13 @@ def main() -> int:
findings = []
walk(data, "", findings)
for lineno, shown in find_continuation_comments(recipe.read_text(errors="replace")):
findings_total += 1
print(f"{recipe}:{lineno}")
print(" comment inside a backslash-continued command -- every")
print(" remaining argument on that command is silently dropped")
print(f" {shown}")
for path, lineno, advice, shown in findings:
findings_total += 1
print(f"{recipe}{path} (line {lineno} of the string)")