. "$BST_LIB" # BST-TIMEOUT: 300 # One probe run -- two 256-combination sweeps, ~90 # decodes, milliseconds -- and ~80 shell extractions. # Same shape and budget as 57-xcb-util-keysyms.test. # xcb-util-wm 0.4.2 -- the XCB ICCCM and EWMH window-manager property helpers # (poky recipes-graphics/xorg-lib). See xcb-util-wm_%.bbappend for what the # port adds. # # --------------------------------------------------------------------------- # WHAT IS MEASURED HERE # # One recipe, TWO libraries, built from two subdirectories of one tarball: # libxcb-icccm.a (hand-written, icccm/) and libxcb-ewmh.a (GENERATED at build # time by m4 from ewmh/*.m4 -- header included, which is why the recipe stages # it out of ${B} and not ${S}). Measured with llvm-nm --defined-only # --extern-only on the wasm32 archives: 82 global symbols in the first and 201 # in the second, 283 together. The probe names 111 of them and the link is what # proves the archives still define those. # # This image has no X server, so what is reachable is the half of the library # that runs before a connection exists -- and for this package that half is the # substance rather than a corner of it. The ICCCM and EWMH property decoders # take an xcb_get_property_reply_t the CALLER owns: # # xcb_icccm_get_wm_hints_from_reply / _wm_size_hints_ / _wm_protocols_ / # _wm_class_ / _wm_transient_for_ / _wm_colormap_windows_ # xcb_ewmh_get_wm_state_from_reply / _wm_window_type_ / _wm_strut_partial_ # xcb_ewmh_get_utf8_strings_from_reply (_NET_WM_NAME forwards here) # # and every ICCCM size-hint and wm-hint SETTER is a struct mutator with no # connection near it. So the encode half is the library's own work too, and # "encode, decode, compare byte for byte" is a statement about this library # rather than about the probe's memcpy. # # OUT OF SCOPE, AND SAID SO RATHER THAN SKIPPED SILENTLY: every xcb_*_get_* / # xcb_*_set_* request wrapper and every _reply taking a connection. Those reach # xcb_get_property_reply(c, ...) or ewmh->connection and would fault here. The # probe REFERENCES them so the link proves the archives define them, and never # calls them -- 82 such, of the 83 link-referenced below. The 83rd is # xcb_ewmh_get_utf8_strings_from_reply, which is referenced AND called: it is # in the table so the census covers the archive symbol the _NET_WM_NAME header # inline forwards to, which a census scoped to archive symbols would otherwise # miss. # # --------------------------------------------------------------------------- # THE TICKET'S PREMISE HOLDS -- WITH ONE EXCEPTION IT DID NOT PREDICT, AND THE # EXCEPTION IS NOT WHERE A READER WOULD LOOK FOR IT # # "ICCCM and EWMH properties are byte buffers with a documented layout, and # this library's decoders work on buffers the caller owns" is right for every # decoder above except one, and the exception is the UTF-8 path. _NET_WM_NAME # compares the reply's type against an INTERNED atom, ewmh->UTF8_STRING, so it # does read an xcb_ewmh_connection_t -- exactly one field of it, and never # ewmh->connection. That struct is declared in full in the installed header # with every atom field public, so the probe builds one, sets UTF8_STRING and # leaves connection NULL. That is public API used as documented, NOT the # mirrored-private-struct route BRINTOS-2339 had to take for the renderutil # text stream, and the suite proves the difference rather than asserting it: # name-otheratom offers the SAME reply under a different interned value and it # is refused, which no compiled-in constant could do. # # A second correction, smaller and worth stating because the ticket names the # file: xcb_ewmh_get_wm_name_from_reply is a `static inline' in the generated # header, not a symbol in libxcb-ewmh.a. It forwards to # xcb_ewmh_get_utf8_strings_from_reply, which is in the archive and is what the # name-* cases actually exercise. # # --------------------------------------------------------------------------- # THE "REJECTED RATHER THAN READ PAST ITS END" CLAUSE IS FALSE UPSTREAM. IT IS # TRUE HERE BECAUSE THE PORT FIXES IT. # # The ticket asks that "a truncated or over-long buffer is asserted to be # rejected by the documented return rather than read past its end". Measured # against xcb-util-wm 0.4.2 as shipped, that was false in two places, and both # were found by running the probe rather than by reading the code: # # * an over-long WM_HINTS property is copied PAST the end of the destination. # xcb_icccm_get_wm_hints_from_reply clamps against sizeof(xcb_size_hints_t) # -- 72 bytes, the SIZE hints -- before memcpy'ing into an # xcb_icccm_wm_hints_t, which is 36. Measured 4/12/20/28/36/36 bytes past # the end for properties of 10/12/14/16/18/20 elements. # # * a zero-length WM_CLASS property is written to one byte BEFORE its value. # xcb_icccm_get_wm_class_from_reply forces a terminator through # `if (len < reply->length * 4) ... else instance_name[len - 1] = 0', and # at len == 0 both sides are 0, so the index is -1. Measured with a 0x5a # sentinel in the reply header's last byte, which came back 0x00. # # Both are genuine faults in the upstream package, neither is a brintOS defect, # and per docs/conventions/no-public-upstream-reports.md both are fixed in our # tree and left there -- 0001-icccm-clamp-WM_HINTS-copy-against-its-own-struct # and 0002-icccm-refuse-a-zero-length-WM_CLASS in the bbappend's SRC_URI, each # carrying BRINTOS-UPSTREAM-FIX rather than BRINTOS-WORKAROUND because neither # works around brintOS. The wmhints-over* and class-zerolen cases below score # the FIXED behaviour, so an image built without those patches goes red on them # and only on them: applying the pair moved exactly 8 of the probe's 194 # measured lines and left the other 186 byte-identical. # # --------------------------------------------------------------------------- # WHAT IS *NOT* REJECTED, AND IS PINNED AS-IS # # A short WM_NORMAL_HINTS property is NOT refused -- there is no minimum-length # guard in xcb_icccm_get_wm_size_hints_from_reply at all. It copies whatever is # there, masks the flag word, and returns 1, so at length 0 the caller gets # back its own uninitialised flags masked to 0xff. sizehints-lengths below # commits that, deliberately and with the caller's fill byte visible in the # expectation, because it is the current contract and a reader meeting # `len0 rc=1 flags=0xa5' should see that it was measured rather than assume the # suite is broken. It is left unpatched: unlike the two above, nothing is # written out of bounds -- the destination is the caller's and the caller # supplied its size. # # --------------------------------------------------------------------------- # EVERY EXPECTATION BELOW WAS MEASURED, NOT PREDICTED # # By building this exact probe source as xcb-util-wm-native (BBCLASSEXTEND in # the bbappend exists for this) and running it on x86-64. The values transfer # to wasm32 because both arms are little-endian with the same 32-bit int # layout, every structure here is a flat array of 32-bit fields the library # moves with memcpy, and no committed value is a pointer -- the alias checks # report a boolean, not an address. `bitbake xcb-util-wm-native' then running # the probe is the whole re-derivation. PROBE=${BST_XCB_UTIL_WM_PROBE:-/usr/libexec/xcb-util-wm-tests/xcb-util-wm-probe} ICCCM_A=/usr/lib/libxcb-icccm.a EWMH_A=/usr/lib/libxcb-ewmh.a ICCCM_H=/usr/include/xcb/xcb_icccm.h EWMH_H=/usr/include/xcb/xcb_ewmh.h ICCCM_PC=/usr/lib/pkgconfig/xcb-icccm.pc EWMH_PC=/usr/lib/pkgconfig/xcb-ewmh.pc tmp=${TMPDIR:-/tmp} out=$tmp/bst-xcb-util-wm-probe.$$.out bst_ok xcb-util-wm/suite-start # --- the installed surface ------------------------------------------------- # Two archives, because this recipe builds two libraries; a port that lost one # subdirectory would still link the probe from the survivor. check xcb-util-wm/icccm-archive-present test -s "$ICCCM_A" check xcb-util-wm/ewmh-archive-present test -s "$EWMH_A" bst_lib_shape xcb-util-wm/lib-shape "/usr/lib/libxcb-icccm.so*" "/usr/lib/libxcb-ewmh.so*" check xcb-util-wm/icccm-header-present test -s "$ICCCM_H" check xcb-util-wm/ewmh-header-present test -s "$EWMH_H" check xcb-util-wm/icccm-pc-present test -s "$ICCCM_PC" check xcb-util-wm/ewmh-pc-present test -s "$EWMH_PC" check_out xcb-util-wm/icccm-pc-names-lib "-lxcb-icccm" cat "$ICCCM_PC" check_out xcb-util-wm/ewmh-pc-names-lib "-lxcb-ewmh" cat "$EWMH_PC" # Both .pc files declare `Requires: xcb'. On a static-only distro nothing # invokes pkg-config --static on a consumer's behalf, so this line is the only # machine-readable record that a static link of either library also needs # libxcb.a -- and libXau.a and libXdmcp.a behind it, through xcb.pc's own # Requires.private. The probe's link line spells all three out for that reason. check_out xcb-util-wm/icccm-pc-requires-xcb "xcb" grep '^Requires' "$ICCCM_PC" check_out xcb-util-wm/ewmh-pc-requires-xcb "xcb" grep '^Requires' "$EWMH_PC" # The EWMH header is m4-generated; a generation step that silently produced # nothing would leave a file that exists and declares no entry point, so the # presence check above is not enough on that side. check xcb-util-wm/ewmh-header-declares-connection \ grep -q "xcb_ewmh_connection_t" "$EWMH_H" check xcb-util-wm/icccm-header-declares-hints \ grep -q "xcb_icccm_wm_hints_t" "$ICCCM_H" check xcb-util-wm/probe-present test -x "$PROBE" # --- run it once ----------------------------------------------------------- if ! "$PROBE" > "$out" 2>&1; then bst_notok xcb-util-wm/probe-runs "probe exited $? : $(head -c 200 "$out")" exit 0 fi if ! grep -q '^==XCBWM-PROBE-END==$' "$out"; then bst_notok xcb-util-wm/probe-runs "probe produced no end sentinel -- truncated run" exit 0 fi bst_ok xcb-util-wm/probe-runs field() { grep "^$1 " "$out" | head -1 | cut -d" " -f2-; } # compare_table : the committed table against the probe's, entry by # entry, so a red names the first line that differs instead of dumping both. compare_table() { _id=$1; _tag=$2 _n=0; _bad= exec 3< "$tmp/bst-xcbwm-$_tag.expected" exec 4< "$tmp/bst-xcbwm-$_tag.got" while IFS= read -r _want <&3; do _n=$((_n + 1)) if IFS= read -r _have <&4; then if [ "$_want" != "$_have" ]; then _bad="entry $_n: want '$_want' got '$_have'"; break fi else _bad="ended at entry $((_n - 1)) -- TRUNCATED, the committed table has more" break fi done if [ -z "$_bad" ] && IFS= read -r _have <&4; then _bad="extra entry past the committed end: '$_have'" fi exec 3<&- 4<&- if [ -n "$_bad" ]; then bst_notok "$_id" "$_bad"; else bst_ok "$_id"; fi } check_eq xcb-util-wm/probe-version "xcb-util-wm-0.4.2" "$(field probe.version)" # --- the widths every committed buffer depends on -------------------------- # The two the upstream fixes are about are the first two: 36 against 72 is the # whole of the WM_HINTS defect. # # ONLY THE POINTER-FREE WIDTHS ARE COMMITTED, and that is the one place where # "measured natively on x86-64, committed for wasm32" could have gone wrong. # Every structure above is a flat array of 32-bit fields and transfers exactly. # The three reply-holder structs -- xcb_ewmh_get_atoms_reply_t, # _utf8_strings_reply_t and xcb_icccm_get_wm_class_reply_t -- carry POINTERS # and are 24 bytes on the build host against 12 on the target, so their sizes # are printed by the probe as diagnostics and deliberately not asserted here. # Nothing else in this suite depends on a pointer width: the alias checks # report a boolean, and every hexdump is of a pointer-free struct or of raw # property bytes. check_eq xcb-util-wm/w-reply-header "32" "$(field sizeof.get_property_reply)" check_eq xcb-util-wm/w-wm-hints "36" "$(field sizeof.wm_hints)" check_eq xcb-util-wm/w-size-hints "72" "$(field sizeof.size_hints)" check_eq xcb-util-wm/w-strut-partial "48" "$(field sizeof.strut_partial)" check_eq xcb-util-wm/w-atom "4" "$(field sizeof.atom)" check_eq xcb-util-wm/n-wm-hints-elements "9" "$(field num.wm_hints_elements)" check_eq xcb-util-wm/n-size-hints-elements "18" "$(field num.wm_size_hints_elements)" # --- the probe re-reads its own reply through libxcb's accessors ----------- # Before any decode is scored. A layout error fails here instead of scoring # ninety plausible-looking wrong answers against a malformed buffer. check_eq xcb-util-wm/construct-value-length "16" "$(field construct.value_length)" check_eq xcb-util-wm/construct-value-offset "1" "$(field construct.value_is_after_header)" check_eq xcb-util-wm/construct-roundtrip "1" "$(field construct.roundtrip)" check_eq xcb-util-wm/construct-wire-length "4" "$(field construct.wire_length)" check_eq xcb-util-wm/construct-f8-length "5" "$(field construct.f8_value_length)" check_eq xcb-util-wm/construct-f8-wire "2" "$(field construct.f8_wire_length)" check_eq xcb-util-wm/construct-ok "1" "$(field construct.ok)" # --- WM_HINTS: the library's own encoders --------------------------------- # Each setter applied to a zeroed struct, so which flag it raises and which # field it writes are pinned individually rather than only through the sweep # digest below. Two are worth reading rather than skimming: set_none CLEARS the # STATE flag (0x0) rather than raising it with a zero state, and there is NO # setter for ICON_POSITION -- it is the one documented WM_HINTS flag this # library exposes no mutator for, which is why icon_position_setter is 0 and # the sweep raises that bit by hand. cat > "$tmp/bst-xcbwm-wmhset.expected" <<'EOF' wmhints.set.input1 flags=0x1 input=0x1 wmhints.set.input0 flags=0x1 input=0x0 wmhints.set.iconic flags=0x2 initial_state=0x3 wmhints.set.normal flags=0x2 initial_state=0x1 wmhints.set.withdrawn flags=0x2 initial_state=0x0 wmhints.set.none flags=0x0 initial_state=0x0 wmhints.set.icon_pixmap flags=0x4 icon_pixmap=0xaa01 wmhints.set.icon_mask flags=0x20 icon_mask=0xaa02 wmhints.set.icon_window flags=0x8 icon_window=0xaa03 wmhints.set.window_group flags=0x40 window_group=0xaa04 wmhints.set.urgency flags=0x100 wmhints.urgency.clear 0 wmhints.urgency.set 256 wmhints.icon_position_setter 0 EOF grep -E "^wmhints\.set\.input1 |^wmhints\.set\.input0 |^wmhints\.set\.iconic |^wmhints\.set\.normal |^wmhints\.set\.withdrawn |^wmhints\.set\.none |^wmhints\.set\.icon_pixmap |^wmhints\.set\.icon_mask |^wmhints\.set\.icon_window |^wmhints\.set\.window_group |^wmhints\.set\.urgency |^wmhints\.urgency\.clear |^wmhints\.urgency\.set |^wmhints\.icon_position_setter " "$out" > "$tmp/bst-xcbwm-wmhset.got" compare_table xcb-util-wm/wmhints-setters wmhset # --- WM_HINTS: all 256 flag combinations, at both accepted lengths --------- # The sweep is over the eight documented bits. It is not a formality: the # decoder is a memcpy plus ONE conditional -- at 8 elements it must force # window_group to XCB_NONE -- so the two digests differ precisely by that # behaviour, under every flag combination. roundtrip9 is the byte-for-byte # re-encode: the decoded struct compared against the bytes that went in, 256 # times. flags-kept is the other half of it: unlike the size hints, this # decoder masks nothing, so every raised bit must survive. check_eq xcb-util-wm/wmhints-sweep-total "256" "$(field wmhints.sweep.total)" check_eq xcb-util-wm/wmhints-sweep-ok9 "256" "$(field wmhints.sweep.ok9)" check_eq xcb-util-wm/wmhints-sweep-ok8 "256" "$(field wmhints.sweep.ok8)" check_eq xcb-util-wm/wmhints-sweep-roundtrip "256" "$(field wmhints.sweep.roundtrip9)" check_eq xcb-util-wm/wmhints-sweep-flags-kept "256" "$(field wmhints.sweep.flags_kept)" check_eq xcb-util-wm/wmhints-sweep-urgency "128" "$(field wmhints.sweep.urgency)" check_eq xcb-util-wm/wmhints-sweep-wg-none "256" "$(field wmhints.sweep.wg_none8)" check_eq xcb-util-wm/wmhints-sweep-fnv9 "f975ded5" "$(field wmhints.sweep.fnv9)" check_eq xcb-util-wm/wmhints-sweep-fnv8 "ed80d755" "$(field wmhints.sweep.fnv8)" # --- WM_HINTS: every field of the fully-populated property ---------------- # The digests above would survive a change that reshuffled fields; these are # the actual bytes and the actual decoded values, including the two SIGNED # fields (icon_x/icon_y), which is where a decoder that widened a field would # show up first. encoded and decoded must be identical -- that IS the round # trip, written out so a reviewer can see it rather than trust a boolean. cat > "$tmp/bst-xcbwm-wmhall.expected" <<'EOF' wmhints.all.encoded 7f0100000100000003000000ff100000ff2000002f010000c1feffffff400000ff500000 wmhints.all.decode 1 wmhints.all.decoded 7f0100000100000003000000ff100000ff2000002f010000c1feffffff400000ff500000 wmhints.all.flags 0x17f wmhints.all.input 1 wmhints.all.initial_state 3 wmhints.all.icon_pixmap 0x10ff wmhints.all.icon_window 0x20ff wmhints.all.icon_x 303 wmhints.all.icon_y -319 wmhints.all.icon_mask 0x40ff wmhints.all.window_group 0x50ff EOF grep -E "^wmhints\.all\.encoded |^wmhints\.all\.decode |^wmhints\.all\.decoded |^wmhints\.all\.flags |^wmhints\.all\.input |^wmhints\.all\.initial_state |^wmhints\.all\.icon_pixmap |^wmhints\.all\.icon_window |^wmhints\.all\.icon_x |^wmhints\.all\.icon_y |^wmhints\.all\.icon_mask |^wmhints\.all\.window_group " "$out" > "$tmp/bst-xcbwm-wmhall.got" compare_table xcb-util-wm/wmhints-all-fields wmhall # --- WM_HINTS: the truncation boundary, both sides ------------------------ # The decoder's guard is `num_elem < ELEMENTS - 1', so 8 is the shortest # accepted length and 7 the first refused one. BOTH sides are committed: a # guard tested only where it rejects is satisfied by a decoder that rejects # everything. At 8 the window_group must come back XCB_NONE (0x0) and at 9 it # must be the sender's value -- that one conditional is the only thing this # function does beyond a memcpy. # # The probe fills the destination with 0xa5 before each of these calls rather # than zeroing it, and that is what makes the 8-element case bite: zeroed, a # decoder that forces window_group to XCB_NONE and one that merely never # copied that far are indistinguishable, and this table stayed green under a # mutation that removed the forcing. Filled, it goes red. cat > "$tmp/bst-xcbwm-wmhlen.expected" <<'EOF' wmhints.len0.rc 0 wmhints.len1.rc 0 wmhints.len2.rc 0 wmhints.len3.rc 0 wmhints.len4.rc 0 wmhints.len5.rc 0 wmhints.len6.rc 0 wmhints.len7.rc 0 wmhints.len8.rc 1 wmhints.len8.window_group 0x0 wmhints.len9.rc 1 wmhints.len9.window_group 0x50ff EOF grep -E "^wmhints\.len0\.rc |^wmhints\.len1\.rc |^wmhints\.len2\.rc |^wmhints\.len3\.rc |^wmhints\.len4\.rc |^wmhints\.len5\.rc |^wmhints\.len6\.rc |^wmhints\.len7\.rc |^wmhints\.len8\.rc |^wmhints\.len8\.window_group |^wmhints\.len9\.rc |^wmhints\.len9\.window_group " "$out" > "$tmp/bst-xcbwm-wmhlen.got" compare_table xcb-util-wm/wmhints-lengths wmhlen # --- WM_HINTS: over-long, and the upstream fix that makes `past 0' true ---- # `past N' is how many bytes beyond the 36-byte destination the library wrote, # counted in a 256-byte buffer the probe owns so the measurement is safe. As # shipped, xcb-util-wm 0.4.2 gives 4/12/20/28/36/36 here, because it clamps # against sizeof(xcb_size_hints_t). With 0001-icccm-clamp-WM_HINTS-copy- # against-its-own-struct.patch it gives 0 at every length, which is what is # committed. An image built without that patch fails exactly these six cases. cat > "$tmp/bst-xcbwm-wmhover.expected" <<'EOF' wmhints.over10.rc 1 past 0 wmhints.over12.rc 1 past 0 wmhints.over14.rc 1 past 0 wmhints.over16.rc 1 past 0 wmhints.over18.rc 1 past 0 wmhints.over20.rc 1 past 0 EOF grep -E "^wmhints\.over10\.rc |^wmhints\.over12\.rc |^wmhints\.over14\.rc |^wmhints\.over16\.rc |^wmhints\.over18\.rc |^wmhints\.over20\.rc " "$out" > "$tmp/bst-xcbwm-wmhover.got" compare_table xcb-util-wm/wmhints-over-long wmhover # --- WM_HINTS: the type, format and null gates ---------------------------- cat > "$tmp/bst-xcbwm-wmhbad.expected" <<'EOF' wmhints.wrongtype.rc 0 wmhints.wrongformat.rc 0 wmhints.null.rc 0 EOF grep -E "^wmhints\.wrongtype\.rc |^wmhints\.wrongformat\.rc |^wmhints\.null\.rc " "$out" > "$tmp/bst-xcbwm-wmhbad.got" compare_table xcb-util-wm/wmhints-gates wmhbad # --- WM_NORMAL_HINTS: the library's own encoders -------------------------- # set_position and set_size each raise a DIFFERENT flag depending on their # user_specified argument (US_* against P_*), which is the only place in this # API where one function writes two different flags, so both arms are pinned. cat > "$tmp/bst-xcbwm-shset.expected" <<'EOF' sizehints.set.position_user flags=0x1 x=11 y=22 sizehints.set.position_prog flags=0x4 x=33 y=44 sizehints.set.size_user flags=0x2 w=55 h=66 sizehints.set.size_prog flags=0x8 w=77 h=88 sizehints.set.min flags=0x10 w=99 h=100 sizehints.set.max flags=0x20 w=101 h=102 sizehints.set.inc flags=0x40 w=103 h=104 sizehints.set.aspect flags=0x80 minn=105 mind=106 maxn=107 maxd=108 sizehints.set.base flags=0x100 w=109 h=110 sizehints.set.gravity flags=0x200 g=9 EOF grep -E "^sizehints\.set\.position_user |^sizehints\.set\.position_prog |^sizehints\.set\.size_user |^sizehints\.set\.size_prog |^sizehints\.set\.min |^sizehints\.set\.max |^sizehints\.set\.inc |^sizehints\.set\.aspect |^sizehints\.set\.base |^sizehints\.set\.gravity " "$out" > "$tmp/bst-xcbwm-shset.got" compare_table xcb-util-wm/sizehints-setters shset # --- WM_NORMAL_HINTS: every field, and the byte-for-byte round trip ------- # All ten flags raised, all eighteen elements populated through the setters # above, decoded, and the decoded struct compared with the encoded bytes. cat > "$tmp/bst-xcbwm-shall.expected" <<'EOF' sizehints.all.encoded fc03000003010000040100000701000008010000090100000a0100000b0100000c0100000d0100000e0100000f010000100100001101000012010000130100001401000001000000 sizehints.all.decode 1 sizehints.all.decoded fc03000003010000040100000701000008010000090100000a0100000b0100000c0100000d0100000e0100000f010000100100001101000012010000130100001401000001000000 sizehints.all.roundtrip 1 sizehints.all.flags 0x3fc sizehints.all.pos 259,260 sizehints.all.size 263,264 sizehints.all.min 265,266 sizehints.all.max 267,268 sizehints.all.inc 269,270 sizehints.all.aspect 271/272,273/274 sizehints.all.base 275,276 sizehints.all.gravity 1 EOF grep -E "^sizehints\.all\.encoded |^sizehints\.all\.decode |^sizehints\.all\.decoded |^sizehints\.all\.roundtrip |^sizehints\.all\.flags |^sizehints\.all\.pos |^sizehints\.all\.size |^sizehints\.all\.min |^sizehints\.all\.max |^sizehints\.all\.inc |^sizehints\.all\.aspect |^sizehints\.all\.base |^sizehints\.all\.gravity " "$out" > "$tmp/bst-xcbwm-shall.got" compare_table xcb-util-wm/sizehints-all-fields shall # --- WM_NORMAL_HINTS: the obsolete layout, and the missing guard ---------- # TWO behaviours in one table, and only one of them is what the ticket # expected. # # The one it expected: ICCCM version 1 had 15 elements and no base size or # window gravity. The decoder gates both on `length >= 18', zeroes the three # fields below that, and masks BASE_SIZE and P_WIN_GRAVITY out of the flag # word -- so every length under 18 comes back flags=0xfc (the source's 0x3fc # with the two high bits gone), base=0,0 and gravity=0, no matter what the # sender put in the flag word. At 18 and above the full 0x3fc and the real # values appear. Over-long is clamped to 18, safely, which is exactly the # clamp WM_HINTS gets wrong. # # The one it did not: there is NO minimum-length guard. Length 0 returns 1, # copies nothing, and hands back the CALLER's own flag word masked to 0xff -- # the probe fills the destination with 0xa5 before the call, so `len0 rc=1 # flags=0xa5' is that fill showing through and is measured, not a bug in this # suite. Left unpatched: nothing is written out of bounds here, the # destination is the caller's and the caller supplied its size. cat > "$tmp/bst-xcbwm-shlen.expected" <<'EOF' sizehints.len0 rc=1 flags=0xa5 base=0,0 gravity=0 sizehints.len1 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len2 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len3 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len4 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len5 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len6 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len7 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len8 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len9 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len10 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len11 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len12 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len13 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len14 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len15 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len16 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len17 rc=1 flags=0xfc base=0,0 gravity=0 sizehints.len18 rc=1 flags=0x3fc base=275,276 gravity=1 sizehints.len19 rc=1 flags=0x3fc base=275,276 gravity=1 sizehints.len20 rc=1 flags=0x3fc base=275,276 gravity=1 EOF grep -E "^sizehints\.len0 |^sizehints\.len1 |^sizehints\.len2 |^sizehints\.len3 |^sizehints\.len4 |^sizehints\.len5 |^sizehints\.len6 |^sizehints\.len7 |^sizehints\.len8 |^sizehints\.len9 |^sizehints\.len10 |^sizehints\.len11 |^sizehints\.len12 |^sizehints\.len13 |^sizehints\.len14 |^sizehints\.len15 |^sizehints\.len16 |^sizehints\.len17 |^sizehints\.len18 |^sizehints\.len19 |^sizehints\.len20 " "$out" > "$tmp/bst-xcbwm-shlen.got" compare_table xcb-util-wm/sizehints-lengths shlen # --- WM_NORMAL_HINTS: the type, format and null gates --------------------- cat > "$tmp/bst-xcbwm-shbad.expected" <<'EOF' sizehints.wrongtype.rc 0 sizehints.wrongformat.rc 0 sizehints.null.rc 0 EOF grep -E "^sizehints\.wrongtype\.rc |^sizehints\.wrongformat\.rc |^sizehints\.null\.rc " "$out" > "$tmp/bst-xcbwm-shbad.got" compare_table xcb-util-wm/sizehints-gates shbad # --- WM_PROTOCOLS: an ATOM list decoded in place -------------------------- # aliases-reply is the one that matters: the decoded list is a POINTER INTO the # reply, not a copy, so `roundtrip' is a comparison of the library's own view # against the committed bytes and the caller must not free the reply before # reading them. A decoder that started copying would still pass roundtrip and # would fail aliases-reply, which is the distinction worth keeping. cat > "$tmp/bst-xcbwm-proto.expected" <<'EOF' protocols.decode 1 protocols.len 4 protocols.atoms 0x121 0x122 0x123 0x124 protocols.aliases_reply 1 protocols.roundtrip 1 protocols.empty.rc 1 len 0 protocols.three.rc 1 len 3 protocols.wrongtype.rc 0 protocols.wrongformat.rc 0 protocols.null.rc 0 EOF grep -E "^protocols\.decode |^protocols\.len |^protocols\.atoms |^protocols\.aliases_reply |^protocols\.roundtrip |^protocols\.empty\.rc |^protocols\.three\.rc |^protocols\.wrongtype\.rc |^protocols\.wrongformat\.rc |^protocols\.null\.rc " "$out" > "$tmp/bst-xcbwm-proto.got" compare_table xcb-util-wm/protocols proto # --- WM_CLASS: the embedded-NUL split, and the second upstream fix -------- # The split is the substance: instance and class are one property separated by # a NUL, and the decoder forces a terminator into the buffer to guarantee it. # The malformed shapes are where the two branches of that force differ -- # `noterm' has no trailing NUL, `no_nul' has none at all and yields an empty # class rather than an error, `three_nul' proves the third field is ignored. # # `zero' and `zerolen' are the fixed defect. As shipped, a zero-length property # takes the else branch (0 < 0 is false) and writes instance_name[-1], one byte # BEFORE the value, on the reply header -- measured by putting 0x5a there and # reading it back as 0x00. With 0002-icccm-refuse-a-zero-length-WM_CLASS.patch # the empty property is refused through the function's own documented failure # return, the sentinel survives, and those are the two lines committed here. cat > "$tmp/bst-xcbwm-class.expected" <<'EOF' class.std rc=1 instance='xterm' class='XTerm' class.noterm rc=1 instance='xterm' class='XTerm' class.empty_inst rc=1 instance='' class='XTerm' class.empty_both rc=1 instance='' class='' class.no_nul rc=1 instance='xterm' class='' class.three_nul rc=1 instance='a' class='b' class.zero rc=0 class.zerolen.rc 0 sentinel 0x5a class.wrongformat.rc 0 class.wrongtype.rc 0 class.null.rc 0 EOF grep -E "^class\.std |^class\.noterm |^class\.empty_inst |^class\.empty_both |^class\.no_nul |^class\.three_nul |^class\.zero |^class\.zerolen\.rc |^class\.wrongformat\.rc |^class\.wrongtype\.rc |^class\.null\.rc " "$out" > "$tmp/bst-xcbwm-class.got" compare_table xcb-util-wm/wm-class class # --- WM_TRANSIENT_FOR and WM_COLORMAP_WINDOWS ----------------------------- # The two remaining caller-supplied ICCCM decoders. transient-empty is the # interesting one: a WINDOW property of the right type but zero length is # REFUSED, which is the guard WM_NORMAL_HINTS is missing. cat > "$tmp/bst-xcbwm-misc.expected" <<'EOF' transient.rc 1 value 0x9001 transient.wrongtype.rc 0 transient.empty.rc 0 transient.null.rc 0 colormap.rc 1 colormap.len 3 colormap.windows 0x9101 0x9102 0x9103 colormap.roundtrip 1 colormap.wrongtype.rc 0 colormap.null.rc 0 EOF grep -E "^transient\.rc |^transient\.wrongtype\.rc |^transient\.empty\.rc |^transient\.null\.rc |^colormap\.rc |^colormap\.len |^colormap\.windows |^colormap\.roundtrip |^colormap\.wrongtype\.rc |^colormap\.null\.rc " "$out" > "$tmp/bst-xcbwm-misc.got" compare_table xcb-util-wm/icccm-window-decoders misc # --- EWMH _NET_WM_STATE and _NET_WM_WINDOW_TYPE --------------------------- # The same generated decoder (DO_REPLY_LIST_VALUES) reached through two # different properties, scored separately rather than one assumed to follow the # other. Both gate on the PREDEFINED XCB_ATOM_ATOM, so neither needs an # interned atom and neither needs a connection. cat > "$tmp/bst-xcbwm-ewatoms.expected" <<'EOF' state.rc 1 state.len 3 state.atoms 0x201 0x202 0x203 state.roundtrip 1 state.wrongtype.rc 0 state.wrongformat.rc 0 state.null.rc 0 state.empty.rc 1 len 0 wtype.rc 1 wtype.len 2 wtype.atoms 0x301 0x302 wtype.roundtrip 1 wtype.wrongtype.rc 0 wtype.null.rc 0 EOF grep -E "^state\.rc |^state\.len |^state\.atoms |^state\.roundtrip |^state\.wrongtype\.rc |^state\.wrongformat\.rc |^state\.null\.rc |^state\.empty\.rc |^wtype\.rc |^wtype\.len |^wtype\.atoms |^wtype\.roundtrip |^wtype\.wrongtype\.rc |^wtype\.null\.rc " "$out" > "$tmp/bst-xcbwm-ewatoms.got" compare_table xcb-util-wm/ewmh-atom-lists ewatoms # --- EWMH _NET_WM_STRUT_PARTIAL ------------------------------------------- # The strictest decoder of the family and the one that answers the ticket's # clause as written, with no patch needed: DO_REPLY_STRUCTURE requires the # value length to EQUAL sizeof(xcb_ewmh_wm_strut_partial_t), so 11 cardinals # and 13 cardinals are BOTH refused by the documented return. All twelve fields # are committed by value and the encoded bytes alongside them. cat > "$tmp/bst-xcbwm-strut.expected" <<'EOF' strut.rc 1 strut.fields 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 strut.roundtrip 1 strut.encoded 000400000104000002040000030400000404000005040000060400000704000008040000090400000a0400000b040000 strut.len10.rc 0 strut.len11.rc 0 strut.len13.rc 0 strut.len14.rc 0 strut.wrongtype.rc 0 strut.wrongformat.rc 0 strut.null.rc 0 EOF grep -E "^strut\.rc |^strut\.fields |^strut\.roundtrip |^strut\.encoded |^strut\.len10\.rc |^strut\.len11\.rc |^strut\.len13\.rc |^strut\.len14\.rc |^strut\.wrongtype\.rc |^strut\.wrongformat\.rc |^strut\.null\.rc " "$out" > "$tmp/bst-xcbwm-strut.got" compare_table xcb-util-wm/ewmh-strut-partial strut # --- EWMH _NET_WM_NAME: the one decoder that reads the connection struct --- # It compares r->type against ewmh->UTF8_STRING, an INTERNED atom, so the probe # supplies an xcb_ewmh_connection_t with that one field set and connection # left NULL -- ewmh-connection-null records that it stayed NULL, which is the # check that the decoder never reached for it. # # name-otheratom is the case that earns the whole arrangement: the SAME reply # offered under a different interned value, refused. A decoder testing a # compiled-in constant could not produce that pair. # # The payload is multi-byte UTF-8 throughout (18 bytes, 12 characters), so a # decoder measuring characters rather than bytes disagrees at name-len; and # name-embedded carries an interior NUL, because _NET_DESKTOP_NAMES is a # NUL-separated list over this same decoder and `strings' is therefore a byte # range and not a C string. cat > "$tmp/bst-xcbwm-name.expected" <<'EOF' ewmh.connection_null 1 ewmh.wm_name_inline_forwards 1 name.rc 1 name.len 18 name.declared_len 18 name.roundtrip 1 name.encoded 6272c3af6e74c3985320e29c9320f09f9a80 name.embedded.rc 1 len 7 name.embedded.second 'two' name.empty.rc 1 len 0 name.wrongtype.rc 0 name.wrongformat.rc 0 name.null.rc 0 name.otheratom.rc 0 EOF grep -E "^ewmh\.connection_null |^ewmh\.wm_name_inline_forwards |^name\.rc |^name\.len |^name\.declared_len |^name\.roundtrip |^name\.encoded |^name\.embedded\.rc |^name\.embedded\.second |^name\.empty\.rc |^name\.wrongtype\.rc |^name\.wrongformat\.rc |^name\.null\.rc |^name\.otheratom\.rc " "$out" > "$tmp/bst-xcbwm-name.got" compare_table xcb-util-wm/ewmh-wm-name name # --- the connection-bearing half, proved present and never called --------- # 83 entry points the probe takes the address of; 82 of them it does not call. # The 83rd, xcb_ewmh_get_utf8_strings_from_reply, is referenced AND called, on # purpose: it is what the _NET_WM_NAME header inline forwards to, so # referencing it is what puts that archive symbol inside a census scoped to # archive symbols. The link is what proves both archives still define all 83; # sink-nonnull is what proves the optimiser did not discard the table and make # that claim vacuous. cat > "$tmp/bst-xcbwm-link.expected" <<'EOF' link.referenced 83 link.sink_nonnull 1 EOF grep -E "^link\.referenced |^link\.sink_nonnull " "$out" > "$tmp/bst-xcbwm-link.got" compare_table xcb-util-wm/link-census link rm -f "$out" "$tmp"/bst-xcbwm-*.expected "$tmp"/bst-xcbwm-*.got bst_ok xcb-util-wm/suite-end