# BST-TIMEOUT: 180 . "$BST_LIB" # microsoft-gsl 4.0.0 (SRCREV a3534567 + three meta-oe backports) -- the C++ # Core Guidelines Support Library: not_null, span, narrow_cast, final_action, # gsl::at and gsl::byte. Header-only: the port ships 15 headers under # /usr/include/gsl and two generated cmake files under # /usr/share/cmake/Microsoft.GSL. No archive, no shared object, no CLI, and no # .pc (upstream ships no pkg-config template -- asserted as an absence below # rather than skipped). # # THIS IS NOT THE GNU SCIENTIFIC LIBRARY. tests/76-gsl.test and # 76-gsl-heavy.test cover THAT package (recipes-support/gsl, GNU GSL 2.7.1); # the two collide on three letters and nothing else. Everything here is # /usr/include/gsl, never /usr/include/gsl/gsl_math.h. # # WHY THERE IS A PROBE. A header-only port ships bytes NO COMPILER ON THIS ARM # NEED EVER HAVE LOOKED AT, so "the files are present" is compatible with a # header set that does not compile. For GSL it is compatible with something # worse than a crash: a not_null that checks nothing, a span that copies where # it should view, a narrow_cast that truncates where it should not. Each of # those returns a PLAUSIBLE answer. The recipe builds microsoft-gsl-probe from # ${S}/include during the port, making do_compile the port's first consumer, # and the probe RUNS here. Sections 1-2 assert the PAYLOAD; sections 3-9 assert # the LIBRARY. # # THE HEADLINE FINDING IS THAT gsl::narrow IS NOT DECLARED ON THIS ARM, and # section 1 establishes it from the shipped bytes while section 4 establishes it # from the compiler. include/gsl/narrow holds the ONLY `throw` in the whole of # include/gsl (two sites, both narrowing_error -- section 1 counts them), and # include/gsl/gsl:28-30 wraps its include in `#ifdef __cpp_exceptions`. The # distro's C++ driver does not define that macro, so the header is never pulled # in: gsl::narrow is not "unavailable because it would throw", it is ABSENT, and # a call to it is a compile error rather than a runtime surprise. What remains # is gsl::narrow_cast -- unchecked and noexcept -- so on this image nothing # diagnoses a lossy narrowing at all. Section 5 measures that loss rather than # describing it. # # THE SECOND FINDING IS THAT EVERY CONTRACT VIOLATION TERMINATES, ON EVERY ARM. # This is NOT the span-lite situation. span-lite auto-discovers the exceptions # state and re-selects its channel, so its behaviour is a consequence of this # toolchain. GSL 4.0.0 has exactly one channel and it is unconditional: # include/gsl/assert:126-127 defines GSL_CONTRACT_CHECK's failure branch as # gsl::details::terminate(), which calls std::terminate(), and there is no # throwing alternative in the tree and no macro to select one. The same probe # built on x86-64 WITH exceptions on terminates identically. Section 9 # establishes it by running four deliberate violations, each in its own # process. # # SECTION 3 IS THE TRIPWIRE. It asserts the compile-time state everything after # it was measured under. A red there means the toolchain or the header # configuration moved -- not necessarily bad news, but every value below would # have to be re-measured rather than merely re-read. # # CROSS-ARM: every value in sections 4-8 is byte-identical to the same probe # source compiled with g++ -Os -g0 -std=c++17 -fno-exceptions on x86-64 and run # there (86 of 94 tags, verified by diff), EXCEPT eight that are word-size facts # by construction: ptr-bits, size-t-bits, index-bytes, sizeof-span-dynamic, # sizeof-span-static, sizeof-subspan-static, sizeof-not-null and # dynamic-extent. Those eight are asserted at their wasm32 values with the # x86-64 reading named beside them, so a reader can tell an arm difference from # a regression without a rebuild. Nothing here is floating point: GSL computes # indices, lengths and pointers, so there is no tolerance to widen and a # divergence is a real finding. # # Style follows 76-span-lite, 76-glm and 76-libeigen: the probe is run ONCE for # the report and its output parsed in a single fork-free loop, because the # runner's per-suite bound is a budget in process spawns rather than in cases. # The four contract arms are the exception and must be separate processes -- # each one dies on purpose. # # The test-plan entry for this port is cited by SUBJECT, not by number: entry # numbers are assigned at merge time and ports land in parallel, so a number # written into a file that SHIPS goes stale the moment another port merges # first (BRINTOS-1135 paid for two image builds learning that). inc=/usr/include/gsl cm=/usr/share/cmake/Microsoft.GSL PT=/usr/libexec/microsoft-gsl-tests # THE PORT SHARES ${inc} WITH GNU GSL AND MUST BE SCOPED OUT OF IT. GNU GSL # 2.7.1 installs 265 gsl_*.h plus test_source.c into this SAME directory, so # `find $inc` reports 281 files belonging to two unrelated projects. The # discriminator is the extension: every microsoft-gsl header is EXTENSIONLESS # (including its five legacy gsl_* forwarding headers), every GNU GSL file # carries one. So each enumerating assertion below is scoped `! -name '*.*'`. # Unscoped, these read as green-or-red about GNU GSL: header-count measured # 281, the "only throwing header" resolved to gsl_message.h, and no-try-catch # tripped on gsl_siman.h -- three reds that say nothing about this port. # --------------------------------------------------------------------------- # 1. The payload. # brintos_local_verify.py loses a suite's FIRST BST result line before the host # parser ever sees it -- BRINTOS-1149, a defect in our own host-side tooling and # not in the runner, the guest or any package. The mechanism was read out of # this port's console dump rather than assumed: the kernel prints its benign # `[k] wasm32: the region holds pending signal(s) the kernel does not ...` audit # line (BRINTOS-783) immediately after ==BST-BEGIN==, and that is what displaces # the first result. Without this decoy the eaten line was microsoft-gsl/inc-dir, # a real assertion. The harness computes its pass count as total-minus-failures, # so the dropped line is counted as a pass -- which is precisely why the eaten # position must never carry an assertion anyone depends on. Remove this case # when 1149 is fixed. bst_ok microsoft-gsl/suite-canary check microsoft-gsl/inc-dir test -d "$inc" check microsoft-gsl/hdr-umbrella test -f "$inc/gsl" check microsoft-gsl/hdr-assert test -f "$inc/assert" check microsoft-gsl/hdr-pointers test -f "$inc/pointers" check microsoft-gsl/hdr-span test -f "$inc/span" check microsoft-gsl/hdr-util test -f "$inc/util" check microsoft-gsl/hdr-narrow test -f "$inc/narrow" check microsoft-gsl/hdr-byte test -f "$inc/byte" check microsoft-gsl/hdr-algorithm test -f "$inc/algorithm" # 15 files: the eight above plus span_ext, string_span, and the five legacy # gsl_* forwarding headers upstream still ships. Pinned as a COUNT so a release # that adds or drops one is a red here rather than a silent change in what a # consumer can include. nh=$(find "$inc" -type f ! -name '*.*' 2>/dev/null | grep -c .) check_eq microsoft-gsl/header-count "15" "$nh" # No build system in ${includedir}: a CMakeLists.txt reaching the installed # tree is what this pins. check_eq microsoft-gsl/no-cmakelists "0" \ "$(find "$inc" -name CMakeLists.txt 2>/dev/null | grep -c .)" # THE STRUCTURAL HALF OF THE HEADLINE FINDING, asserted against the SHIPPED # bytes rather than against upstream's repository. Exactly one header contains # a `throw` statement, and it is narrow. (The match is word-anchored on # purpose: util carries the word inside a comment -- "terminate if throws" -- # and a substring grep would report two files and hide the fact.) tf=$(find "$inc" -type f ! -name '*.*' -exec grep -lw 'throw' {} + 2>/dev/null | grep -c .) check_eq microsoft-gsl/throwing-header-count "1" "$tf" check_eq microsoft-gsl/throwing-header-is-narrow "$inc/narrow" \ "$(find "$inc" -type f ! -name '*.*' -exec grep -lw 'throw' {} + 2>/dev/null | head -1)" check_eq microsoft-gsl/narrow-throw-sites "2" \ "$(grep -cw 'throw' "$inc/narrow" 2>/dev/null)" # ...and no try/catch anywhere, which is why every OTHER header compiles here. check_eq microsoft-gsl/no-try-catch "0" \ "$(find "$inc" -type f ! -name '*.*' -exec grep -lwE 'try|catch' {} + 2>/dev/null | grep -c .)" # The guard that makes the absence happen, in the umbrella header a consumer # actually includes. Asserted as text here and as BEHAVIOUR in section 4. u=$(cat "$inc/gsl" 2>/dev/null) case "$u" in *"#ifdef __cpp_exceptions"*) ug=present ;; *) ug=absent ;; esac check_eq microsoft-gsl/umbrella-exceptions-guard "present" "$ug" case "$u" in *"#include "*) un=present ;; *) un=absent ;; esac check_eq microsoft-gsl/umbrella-includes-narrow "present" "$un" # The contract channel, in the header that defines it. GSL 4.0.0 ships ONE # arm; a header that grew a throwing alternative would show up here. a=$(cat "$inc/assert" 2>/dev/null) case "$a" in *"GSL_CONTRACT_CHECK"*) ac=present ;; *) ac=absent ;; esac check_eq microsoft-gsl/assert-contract-check "present" "$ac" case "$a" in *"std::terminate();"*) at=present ;; *) at=absent ;; esac check_eq microsoft-gsl/assert-terminate-arm "present" "$at" case "$a" in *"#define Expects(cond)"*) ae=present ;; *) ae=absent ;; esac check_eq microsoft-gsl/assert-expects "present" "$ae" case "$a" in *"#define Ensures(cond)"*) an=present ;; *) an=absent ;; esac check_eq microsoft-gsl/assert-ensures "present" "$an" # The names a consumer writes. p=$(cat "$inc/pointers" 2>/dev/null) case "$p" in *"class not_null"*) pn=present ;; *) pn=absent ;; esac check_eq microsoft-gsl/pointers-not-null "present" "$pn" case "$p" in *"class strict_not_null"*) ps=present ;; *) ps=absent ;; esac check_eq microsoft-gsl/pointers-strict-not-null "present" "$ps" # not_null(std::nullptr_t) = delete is the COMPILE-TIME half of the guarantee # and the runtime arm in section 9 can never reach it. case "$p" in *"not_null(std::nullptr_t) = delete"*) pd=present ;; *) pd=absent ;; esac check_eq microsoft-gsl/pointers-nullptr-deleted "present" "$pd" # --------------------------------------------------------------------------- # 2. The integration surface. # # NO .pc FILE, AND THAT IS UPSTREAM'S ARRANGEMENT rather than a defect of this # port -- GSL ships no pkg-config template at all. It is asserted as an absence # so that an upstream release which starts shipping one shows up here as a red # with this comment attached, instead of passing unnoticed. # gsl.pc BELONGS TO GNU GSL, so the absence has to be asserted around it: a # bare '*gsl*' glob matches that file and can never be 0 on this image. The # second case pins the ownership, so a future microsoft-gsl that starts # shipping one shows up as a red naming the file rather than as a count. check_eq microsoft-gsl/no-pkgconfig "0" \ "$(find /usr/lib/pkgconfig /usr/share/pkgconfig -name '*gsl*.pc' 2>/dev/null \ | sed 's|.*/||' | grep -vx 'gsl.pc' | grep -c .)" check_eq microsoft-gsl/pkgconfig-is-gnu-gsl-only "gsl.pc" \ "$(find /usr/lib/pkgconfig /usr/share/pkgconfig -name '*gsl*.pc' 2>/dev/null \ | sed 's|.*/||' | sort | tr '\n' ' ' | sed 's/ *$//')" # ${datadir}/cmake, NOT ${libdir}/cmake -- GSL's install rule uses # CMAKE_INSTALL_DATADIR, and scarthgap's bitbake.conf:349-353 puts both in # FILES:${PN}-dev. The path is pinned because a consumer's find_package looks # in one of them. check microsoft-gsl/cmake-dir test -d "$cm" check microsoft-gsl/cmake-config test -f "$cm/Microsoft.GSLConfig.cmake" check microsoft-gsl/cmake-version test -f "$cm/Microsoft.GSLConfigVersion.cmake" # TWO files, not three: unlike span-lite, GSL's export writes the imported # target INTO the Config file rather than a separate *Targets.cmake. check_eq microsoft-gsl/cmake-file-count "2" \ "$(find "$cm" -type f 2>/dev/null | grep -c .)" cfgv=$(cat "$cm/Microsoft.GSLConfigVersion.cmake" 2>/dev/null) case "$cfgv" in *'set(PACKAGE_VERSION "4.0.0")'*) pkv=ok ;; *) pkv=absent ;; esac check_eq microsoft-gsl/cmake-pkg-version "ok" "$pkv" cfg=$(cat "$cm/Microsoft.GSLConfig.cmake" 2>/dev/null) case "$cfg" in *'add_library(Microsoft.GSL::GSL INTERFACE IMPORTED)'*) ti=interface ;; *) ti=other ;; esac check_eq microsoft-gsl/cmake-target-interface "interface" "$ti" # The include path a consumer compiles against must be the SHIPPED one, not a # build-tree path -- the failure a bare existence check cannot see. case "$cfg" in *'_IMPORT_PREFIX}/include'*) tp=relative ;; *) tp=other ;; esac check_eq microsoft-gsl/cmake-include-relative "relative" "$tp" # The minimum standard upstream advertises to consumers. C++14, even though the # probe is built at C++17 -- section 3 records why that choice is not cosmetic. case "$cfg" in *'INTERFACE_COMPILE_FEATURES "cxx_std_14"'*) tc=cxx_std_14 ;; *) tc=other ;; esac check_eq microsoft-gsl/cmake-compile-feature "cxx_std_14" "$tc" # A build-tree path leaking into a shipped cmake file is invisible to every # check above and is what this one catches. case "$cfg" in *"/usr/src/debug"*|*"/build/tmp"*|*"tmp-glibc"*|*"workdir"*) tl=leak ;; *) tl=clean ;; esac check_eq microsoft-gsl/cmake-no-buildpath "clean" "$tl" # --------------------------------------------------------------------------- # 3-9. The probe. Run ONCE for the report; everything below reads the capture. if [ ! -x "$PT/microsoft-gsl-probe" ]; then bst_skip microsoft-gsl/probe-present "microsoft-gsl-probe not installed on this image" exit 0 fi bst_ok microsoft-gsl/probe-present # The probe's size is a functional property on this target, not trivia: the # guest stages an exec image as a segment list capped at WASM32_IMG_MAX_SEGS, # so a large image is refused -ENOMEM on a fragmented heap before it runs # (nopoll measured exactly that at 41 MB). Read against this arm's floor rather # than on its own -- an empty main() costs ~2.3 MB here, glibc and libc++ # merged whole, so most of this is baseline and not GSL. 8 MB is far above what # the port stages (2,523,365 bytes measured 2026-08-21) and far below where the # cap bites. sz=$(( $(wc -c < "$PT/microsoft-gsl-probe") )) if [ "$sz" -lt 8388608 ]; then bst_ok microsoft-gsl/probe-size else bst_notok microsoft-gsl/probe-size "probe is $sz bytes, over the 8388608 ceiling"; fi out=$("$PT/microsoft-gsl-probe" 2>&1); prc=$? # Parse every P- line in ONE pass. No forks in the loop. P_CXXSTD=; P_ELIDED=; P_EXC=; P_NARROWH=; P_CONTRH=; P_KMODE=; P_MSVCW=; P_NOIO= P_STDBYTE=; P_BYTEIS=; P_PTRB=; P_SZTB=; P_INTB=; P_IDXB=; P_IDXPD= P_SPANDYN=; P_SPANST=; P_SUBST=; P_NNB=; P_NNZERO=; P_DYNEXT=; P_STATEXT4= P_NNNULLT=; P_NNDEF=; P_NNRAW=; P_NNCONV=; P_NNIMPL=; P_SNNIMPL=; P_SNNEXPL= P_NCNOEX= P_NNDEREF=; P_NNALIAS=; P_NNARROW=; P_NNIRAW=; P_NNMAKE=; P_NNREBIND= P_NNSHARED=; P_NNEQ=; P_NNHASH=; P_SNNDEREF= P_NFITS=; P_NSCLO=; P_NSCHI=; P_NU32=; P_NTRUNC=; P_NSIGN=; P_NIDX= P_CASZ=; P_CAAL=; P_SASZ=; P_SAAL=; P_SACSZ=; P_VSZ=; P_VAL=; P_STRSZ=; P_STRAL= P_PCSZ=; P_PPSZ=; P_DEFSZ=; P_DEFE=; P_STSZ=; P_ST2DYN= P_SIZE=; P_SIZEB=; P_ISUM=; P_FRONT=; P_BACK=; P_IFWD=; P_IREV= P_F3=; P_L3=; P_SOFF=; P_SCNT=; P_TF3=; P_TS12=; P_CHAIN=; P_DEG= P_ATC=; P_ATV=; P_ATI= P_MUT=; P_VMUT=; P_ABSZ=; P_BLOW=; P_WBW=; P_COPY= P_FAORD=; P_FARUNS=; P_FAMOVED=; P_FAMRUNS=; P_FAEARLY= P_CHECKS=; P_FAILURES=; P_RESULT= oIFS=$IFS; IFS=' ' for line in $out; do tag=${line%% *}; val=${line#* } case "$tag" in P-CXXSTD) P_CXXSTD=$val ;; P-CONTRACTS-ELIDED) P_ELIDED=$val ;; P-CXX-EXCEPTIONS) P_EXC=$val ;; P-GSL-NARROW-HEADER) P_NARROWH=$val ;; P-GSL-CONTRACTS-HEADER) P_CONTRH=$val ;; P-GSL-KERNEL-MODE) P_KMODE=$val ;; P-GSL-MSVC-NOEXCEPT-WORKAROUND) P_MSVCW=$val ;; P-GSL-NO-IOSTREAMS) P_NOIO=$val ;; P-GSL-USE-STD-BYTE) P_STDBYTE=$val ;; P-GSL-BYTE-IS-STD-BYTE) P_BYTEIS=$val ;; P-PTR-BITS) P_PTRB=$val ;; P-SIZE-T-BITS) P_SZTB=$val ;; P-INT-BYTES) P_INTB=$val ;; P-INDEX-BYTES) P_IDXB=$val ;; P-INDEX-IS-PTRDIFF) P_IDXPD=$val ;; P-SPAN-DYN-BYTES) P_SPANDYN=$val ;; P-SPAN-STATIC-BYTES) P_SPANST=$val ;; P-SPAN-SUBSPAN-STATIC-BYTES) P_SUBST=$val ;; P-NOTNULL-BYTES) P_NNB=$val ;; P-NOTNULL-ZERO-OVERHEAD) P_NNZERO=$val ;; P-DYNAMIC-EXTENT) P_DYNEXT=$val ;; P-STATIC-EXTENT-4) P_STATEXT4=$val ;; P-NOTNULL-FROM-NULLPTR-T) P_NNNULLT=$val ;; P-NOTNULL-DEFAULT-CTOR) P_NNDEF=$val ;; P-NOTNULL-FROM-RAW) P_NNRAW=$val ;; P-NOTNULL-CONV-TO-RAW) P_NNCONV=$val ;; P-NOTNULL-IMPLICIT-FROM-RAW) P_NNIMPL=$val ;; P-STRICT-NOTNULL-IMPLICIT-FROM-RAW) P_SNNIMPL=$val ;; P-STRICT-NOTNULL-EXPLICIT-FROM-RAW) P_SNNEXPL=$val ;; P-NARROW-CAST-NOEXCEPT) P_NCNOEX=$val ;; P-NN-DEREF) P_NNDEREF=$val ;; P-NN-ALIASES) P_NNALIAS=$val ;; P-NN-ARROW) P_NNARROW=$val ;; P-NN-IMPLICIT-RAW) P_NNIRAW=$val ;; P-NN-MAKE) P_NNMAKE=$val ;; P-NN-REBIND) P_NNREBIND=$val ;; P-NN-SHARED) P_NNSHARED=$val ;; P-NN-EQ) P_NNEQ=$val ;; P-NN-HASH-FORWARDS) P_NNHASH=$val ;; P-STRICT-NN-DEREF) P_SNNDEREF=$val ;; P-NARROW-FITS) P_NFITS=$val ;; P-NARROW-SCHAR-LO) P_NSCLO=$val ;; P-NARROW-SCHAR-HI) P_NSCHI=$val ;; P-NARROW-U32-MAX) P_NU32=$val ;; P-NARROW-TRUNCATES) P_NTRUNC=$val ;; P-NARROW-SIGN-FLIP) P_NSIGN=$val ;; P-NARROW-TO-INDEX) P_NIDX=$val ;; P-CARRAY-SIZE) P_CASZ=$val ;; P-CARRAY-ALIASES) P_CAAL=$val ;; P-STDARRAY-SIZE) P_SASZ=$val ;; P-STDARRAY-ALIASES) P_SAAL=$val ;; P-STDARRAY-CONST-SIZE) P_SACSZ=$val ;; P-VECTOR-SIZE) P_VSZ=$val ;; P-VECTOR-ALIASES) P_VAL=$val ;; P-STRING-SIZE) P_STRSZ=$val ;; P-STRING-ALIASES) P_STRAL=$val ;; P-PTRCOUNT-SIZE) P_PCSZ=$val ;; P-PTRPAIR-SIZE) P_PPSZ=$val ;; P-DEFAULT-SIZE) P_DEFSZ=$val ;; P-DEFAULT-EMPTY) P_DEFE=$val ;; P-STATIC-SIZE) P_STSZ=$val ;; P-STATIC-TO-DYNAMIC) P_ST2DYN=$val ;; P-SIZE) P_SIZE=$val ;; P-SIZE-BYTES) P_SIZEB=$val ;; P-INDEX-SUM) P_ISUM=$val ;; P-FRONT) P_FRONT=$val ;; P-BACK) P_BACK=$val ;; P-ITER-FWD) P_IFWD=$val ;; P-ITER-REV) P_IREV=$val ;; P-FIRST3) P_F3=$val ;; P-LAST3) P_L3=$val ;; P-SUBSPAN-OFFSET) P_SOFF=$val ;; P-SUBSPAN-COUNT) P_SCNT=$val ;; P-TFIRST3) P_TF3=$val ;; P-TSUBSPAN12) P_TS12=$val ;; P-CHAIN) P_CHAIN=$val ;; P-DEGENERATE-OK) P_DEG=$val ;; P-AT-CARRAY) P_ATC=$val ;; P-AT-VECTOR) P_ATV=$val ;; P-AT-INITLIST) P_ATI=$val ;; P-MUTATE) P_MUT=$val ;; P-VECTOR-MUTATE) P_VMUT=$val ;; P-AS-BYTES-SIZE) P_ABSZ=$val ;; P-BYTES-LOW) P_BLOW=$val ;; P-WBYTES-WRITE) P_WBW=$val ;; P-COPY) P_COPY=$val ;; P-FA-ORDER) P_FAORD=$val ;; P-FA-RUNS) P_FARUNS=$val ;; P-FA-MOVED) P_FAMOVED=$val ;; P-FA-MOVED-RUNS) P_FAMRUNS=$val ;; P-FA-EARLY-RETURN) P_FAEARLY=$val ;; P-CHECKS) P_CHECKS=$val ;; P-FAILURES) P_FAILURES=$val ;; P-RESULT) P_RESULT=$val ;; esac done IFS=$oIFS # The probe's own verdict, first. Its 59 internal assertions are a whole-run # gate; the named cases below are what makes a red attributable without a # rebuild, and both have to hold. check_eq microsoft-gsl/probe-rc "0" "$prc" check_eq microsoft-gsl/probe-result "OK" "$P_RESULT" check_eq microsoft-gsl/probe-failures "0" "$P_FAILURES" # A lower bound, not the exact count: a later ticket adding a case to the probe # should not redden this suite, but a probe that silently stopped emitting # should. if [ -n "$P_CHECKS" ] && [ "$P_CHECKS" -ge 59 ] 2>/dev/null; then bst_ok microsoft-gsl/probe-check-count else bst_notok microsoft-gsl/probe-check-count "probe reported '$P_CHECKS' checks, want >= 59"; fi # --------------------------------------------------------------------------- # 3. The tripwire: the compile-time state every value below was measured under. check_eq microsoft-gsl/cxxstd "201703" "$P_CXXSTD" # The probe was NOT built with its own contracts-elided control. If this ever # reads 1, section 9's "did not survive" cases are measuring a neutered binary # and mean nothing -- which is exactly why the flag is reported rather than # assumed. check_eq microsoft-gsl/contracts-not-elided "0" "$P_ELIDED" # 0 is the DISTRO's state, not a flag this recipe passes: the C++ driver # compiles without exceptions, and the probe asserts it rather than assuming. check_eq microsoft-gsl/cxx-exceptions "0" "$P_EXC" # ...and this is the consequence, read off GSL's own include guard. THE # HEADLINE FINDING, from the compiler's side. gsl::narrow is not declared. check_eq microsoft-gsl/narrow-header-absent "0" "$P_NARROWH" check_eq microsoft-gsl/contracts-header-present "1" "$P_CONTRH" # Both MSVC-only. With neither set, gsl::details::terminate() is a plain # std::terminate(), which is what makes section 9's status interpretable. check_eq microsoft-gsl/kernel-mode "0" "$P_KMODE" check_eq microsoft-gsl/msvc-noexcept-workaround "0" "$P_MSVCW" check_eq microsoft-gsl/iostreams-enabled "0" "$P_NOIO" # At C++17 against libc++ >= 5000, gsl::byte IS std::byte -- and gsl::to_byte, # which lives only in the fallback branch, does not exist on this arm. A # consumer writing it gets a compile error; this pair is where that is # recorded. At C++14 both readings would flip. check_eq microsoft-gsl/use-std-byte "1" "$P_STDBYTE" check_eq microsoft-gsl/byte-is-std-byte "1" "$P_BYTEIS" check_eq microsoft-gsl/narrow-cast-noexcept "1" "$P_NCNOEX" # The eight word-size facts. x86-64 reads 64 / 64 / 8 / 16 / 8 / 8 / 8 / # 18446744073709551615 for these; they are the only values in this suite that # differ across the two arms, and they differ by construction. check_eq microsoft-gsl/ptr-bits "32" "$P_PTRB" check_eq microsoft-gsl/size-t-bits "32" "$P_SZTB" check_eq microsoft-gsl/int-bytes "4" "$P_INTB" check_eq microsoft-gsl/index-bytes "4" "$P_IDXB" check_eq microsoft-gsl/index-is-ptrdiff "1" "$P_IDXPD" # GSL's storage_type DERIVES from extent_type, which for a static extent # holds only a `static constexpr` -- so it is empty and the empty-base # optimisation shrinks a static-extent span to a bare pointer. That is a real # ABI difference from span-lite ON THIS SAME IMAGE, which stores pointer + size # for both extents and measures 8 either way. Pinned here so the contrast is # measured rather than remembered. check_eq microsoft-gsl/sizeof-span-dynamic "8" "$P_SPANDYN" check_eq microsoft-gsl/sizeof-span-static "4" "$P_SPANST" check_eq microsoft-gsl/sizeof-subspan-static "4" "$P_SUBST" # "Zero size overhead over T" is not a slogan a file listing can check. check_eq microsoft-gsl/sizeof-not-null "4" "$P_NNB" check_eq microsoft-gsl/not-null-zero-overhead "1" "$P_NNZERO" check_eq microsoft-gsl/dynamic-extent "4294967295" "$P_DYNEXT" check_eq microsoft-gsl/static-extent-4 "4" "$P_STATEXT4" # The COMPILE-TIME half of not_null's guarantee, which section 9 cannot reach. # A literal nullptr is rejected by the compiler, so the Expects in the # constructor only ever fires on a pointer that is null at RUN time. A port # that tested only the runtime path would leave the stronger, cheaper guarantee # unpinned. check_eq microsoft-gsl/not-null-rejects-nullptr-t "0" "$P_NNNULLT" check_eq microsoft-gsl/not-null-no-default-ctor "0" "$P_NNDEF" check_eq microsoft-gsl/not-null-from-raw "1" "$P_NNRAW" check_eq microsoft-gsl/not-null-converts-to-raw "1" "$P_NNCONV" # The whole difference between the two types, and no runtime case can see it: # not_null converts implicitly from a raw pointer, strict_not_null does not. check_eq microsoft-gsl/not-null-implicit-from-raw "1" "$P_NNIMPL" check_eq microsoft-gsl/strict-not-null-not-implicit "0" "$P_SNNIMPL" check_eq microsoft-gsl/strict-not-null-explicit-from-raw "1" "$P_SNNEXPL" # --------------------------------------------------------------------------- # 4. not_null -- the positive path. The negative path is section 9. check_eq microsoft-gsl/nn-deref "41" "$P_NNDEREF" # The view is not a copy: writing through the not_null is visible in the object # it was built from. A not_null that copied would pass every read. check_eq microsoft-gsl/nn-aliases "77" "$P_NNALIAS" check_eq microsoft-gsl/nn-arrow "34" "$P_NNARROW" check_eq microsoft-gsl/nn-implicit-raw "1" "$P_NNIRAW" check_eq microsoft-gsl/nn-make-not-null "1" "$P_NNMAKE" # A rebind must take, and must not smuggle the old pointer through. check_eq microsoft-gsl/nn-rebind "77" "$P_NNREBIND" # A smart-pointer not_null is a different instantiation with a different # Expects site, and it is the one real code reaches for ownership. check_eq microsoft-gsl/nn-shared-ptr "19" "$P_NNSHARED" check_eq microsoft-gsl/nn-equality "1" "$P_NNEQ" # hash forwards to the pointee type's hash -- the property that lets a not_null # be a map key. check_eq microsoft-gsl/nn-hash-forwards "1" "$P_NNHASH" check_eq microsoft-gsl/strict-nn-deref "77" "$P_SNNDEREF" # --------------------------------------------------------------------------- # 5. narrow_cast, and the loss nothing on this arm diagnoses. check_eq microsoft-gsl/narrow-fits "300" "$P_NFITS" check_eq microsoft-gsl/narrow-schar-lo "-128" "$P_NSCLO" check_eq microsoft-gsl/narrow-schar-hi "127" "$P_NSCHI" check_eq microsoft-gsl/narrow-u32-max "4294967295" "$P_NU32" # 44 == 300 & 0xff. THIS is why gsl::narrow's absence matters: with the checked # form compiled out, a lossy narrowing on this image is silent. The value is # asserted rather than described, so a toolchain that started trapping instead # would surface as a red here. check_eq microsoft-gsl/narrow-truncates "44" "$P_NTRUNC" # Sign is reinterpreted, not clamped. check_eq microsoft-gsl/narrow-sign-flip "4294967295" "$P_NSIGN" check_eq microsoft-gsl/narrow-to-index "7" "$P_NIDX" # --------------------------------------------------------------------------- # 6. span construction. WHICH constructor an argument selects is the fact -- # every one produces a span that looks identical afterwards, and every # "aliases" case is the difference between a VIEW and a copy. check_eq microsoft-gsl/ctor-carray-size "5" "$P_CASZ" check_eq microsoft-gsl/ctor-carray-aliases "1" "$P_CAAL" check_eq microsoft-gsl/ctor-stdarray-size "6" "$P_SASZ" check_eq microsoft-gsl/ctor-stdarray-aliases "1" "$P_SAAL" check_eq microsoft-gsl/ctor-stdarray-const "6" "$P_SACSZ" check_eq microsoft-gsl/ctor-vector-size "7" "$P_VSZ" check_eq microsoft-gsl/ctor-vector-aliases "1" "$P_VAL" # 11, not 12: a span over a std::string counts characters, not the NUL. check_eq microsoft-gsl/ctor-string-size "11" "$P_STRSZ" check_eq microsoft-gsl/ctor-string-aliases "1" "$P_STRAL" check_eq microsoft-gsl/ctor-ptr-count "3" "$P_PCSZ" # 4, not 5: the pointer pair is [first, last), and an implementation that took # it inclusively would be one too long. check_eq microsoft-gsl/ctor-ptr-pair-halfopen "4" "$P_PPSZ" check_eq microsoft-gsl/ctor-default-size "0" "$P_DEFSZ" check_eq microsoft-gsl/ctor-default-empty "1" "$P_DEFE" check_eq microsoft-gsl/ctor-static-size "5" "$P_STSZ" check_eq microsoft-gsl/ctor-static-to-dynamic "5" "$P_ST2DYN" # --------------------------------------------------------------------------- # 7. Size, access, iteration, subviews, and gsl::at. check_eq microsoft-gsl/size "5" "$P_SIZE" # 20 == 5 elements x 4 bytes. The PAIR is the fact: a span substituted for a # byte-oriented pointer/length pair that confuses the two reads four times too # far, and no existence check can see it. check_eq microsoft-gsl/size-bytes "20" "$P_SIZEB" check_eq microsoft-gsl/index-sum "15" "$P_ISUM" check_eq microsoft-gsl/front "1" "$P_FRONT" check_eq microsoft-gsl/back "5" "$P_BACK" check_eq microsoft-gsl/iter-forward "1,2,3,4,5" "$P_IFWD" # Reverse iteration is a separate implementation and is where an off-by-one # shows up as a value rather than a crash. check_eq microsoft-gsl/iter-reverse "5,4,3,2,1" "$P_IREV" check_eq microsoft-gsl/first3 "1,2,3" "$P_F3" check_eq microsoft-gsl/last3 "3,4,5" "$P_L3" check_eq microsoft-gsl/subspan-offset "3,4,5" "$P_SOFF" check_eq microsoft-gsl/subspan-count "2,3,4" "$P_SCNT" # The template forms take their bounds at compile time and are a distinct code # path from the runtime ones above. check_eq microsoft-gsl/template-first3 "1,2,3" "$P_TF3" check_eq microsoft-gsl/template-subspan "2,3" "$P_TS12" check_eq microsoft-gsl/subspan-chain "3,4" "$P_CHAIN" # The legal degenerate subviews -- first(0), last(0), subspan(size()), # subspan(n,0). Each is IN contract: an implementation that terminated on these # would break correct code, so they are asserted as passes and NOT left to # section 9. check_eq microsoft-gsl/degenerate-in-contract "1" "$P_DEG" # gsl::at is the Guidelines' bounds-checked accessor, and it is a DIFFERENT # check site from span::operator[] -- section 9 drives both. check_eq microsoft-gsl/at-carray "3" "$P_ATC" check_eq microsoft-gsl/at-vector "8" "$P_ATV" check_eq microsoft-gsl/at-initlist "6" "$P_ATI" # --------------------------------------------------------------------------- # 8. Aliasing, bytes, copy, and final_action. check_eq microsoft-gsl/mutate-through-span "7,42,7,99" "$P_MUT" check_eq microsoft-gsl/mutate-through-vector "77" "$P_VMUT" # 16 == 4 ints x 4 bytes -- the other half of the size/size_bytes distinction. check_eq microsoft-gsl/as-bytes-size "16" "$P_ABSZ" # 42 == 0x2a in the low byte on a little-endian target. The VALUE is asserted # rather than the endianness, so a big-endian arm surfaces as a red naming the # number. check_eq microsoft-gsl/bytes-low "42" "$P_BLOW" check_eq microsoft-gsl/as-writable-bytes "5" "$P_WBW" check_eq microsoft-gsl/copy "77,2,3" "$P_COPY" # Reverse order of construction, because these are destructors. A scope guard # that fired in declaration order would release in the wrong order and the # error would be silent. check_eq microsoft-gsl/final-action-order "cba" "$P_FAORD" check_eq microsoft-gsl/final-action-runs "3" "$P_FARUNS" # A MOVED-FROM final_action must not fire. A double-run is the classic scope # guard defect and it is silent. check_eq microsoft-gsl/final-action-moved "x" "$P_FAMOVED" check_eq microsoft-gsl/final-action-moved-runs "1" "$P_FAMRUNS" check_eq microsoft-gsl/final-action-early-return "1" "$P_FAEARLY" # --------------------------------------------------------------------------- # 9. The contract arms -- the second finding, established rather than asserted # from the header text. # # Four arms, four DIFFERENT check sites: not_null's constructor (pointers), # span::operator[] (span), gsl::at (util), and span::first (span again, via a # count rather than an index). Each is its own process because each one DIES on # purpose. # # Three statements per arm, and all three are needed: that the programme # reached the violation (PRE), that it did not survive it (no SURVIVED line), # and that the shell saw a non-zero status. PRE alone would pass on a binary # that could not start; a non-zero status alone would pass on one that crashed # for an unrelated reason. # # THE "no-survivor" CASE IS LOAD-BEARING AND WAS PROVED SO RATHER THAN ASSUMED. # A guard adopted because it passes is not evidence -- one asserting nothing # also passes. The probe carries a control, GSL_PROBE_ELIDE_CONTRACTS, which # suppresses and defines Expects/Ensures as no-ops BEFORE # is read. Built that way on x86-64, all four arms print the SURVIVED # line and exit 0: # # contract-not-null P-CONTRACT-SURVIVED 1 (a not_null holding null) # contract-span-index P-CONTRACT-SURVIVED -1 (garbage past the end) # contract-at P-CONTRACT-SURVIVED -1 (garbage past the end) # contract-subspan P-CONTRACT-SURVIVED 9 (a span of 9 over 3 ints) # # Every one of those is a SILENT WRONG ANSWER, which is the mode this port # exists to rule out -- so these cases fail against the state that has the # defect, and that is what makes them mean something. # # The measured status is recorded in its own case rather than folded into the # died/survived pair, so a platform change in how an aborted process is # reported is visible as ONE red naming the number, not as a suite that fails # to distinguish "terminated correctly" from "terminated differently". for arm in contract-not-null contract-span-index contract-at contract-subspan; do cout=$("$PT/microsoft-gsl-probe" "$arm" 2>&1); crc=$? case "$cout" in *"P-CONTRACT-PRE"*) pre=1 ;; *) pre=0 ;; esac check_eq "microsoft-gsl/$arm-reached" "1" "$pre" case "$cout" in *"P-CONTRACT-SURVIVED"*) surv=1 ;; *) surv=0 ;; esac check_eq "microsoft-gsl/$arm-no-survivor" "0" "$surv" if [ "$crc" -ne 0 ]; then bst_ok "microsoft-gsl/$arm-died" else bst_notok "microsoft-gsl/$arm-died" "exited 0; the GSL contract did not fire"; fi # 134 == 128 + SIGABRT. Measured identically on x86-64, where exceptions # are ON -- which is the evidence that this is upstream's single channel # rather than a consequence of this toolchain. check_eq "microsoft-gsl/$arm-status" "134" "$crc" done