-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcheck-surface.sh
More file actions
executable file
·134 lines (125 loc) · 5.99 KB
/
Copy pathcheck-surface.sh
File metadata and controls
executable file
·134 lines (125 loc) · 5.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env bash
# Compares an implementation's exported C surface against SURFACE.txt.
#
# Clause 9 of the specification requires this comparison. It examines the
# artefact rather than the behaviour, so it is neither slow nor subject to
# incomplete coverage, and it detects the one freedom an implementation retains
# after the language has removed the others: the addition of names.
#
# check-surface.sh [--complete] <surface-list> <object-or-archive>...
#
# Without --complete, an absent name denotes an interface the implementation
# does not provide, which clause 3 permits. With --complete, every name in the
# list is required, which is what an implementation claiming the whole
# specification asserts.
set -euo pipefail
complete=0
if [ "${1:-}" = "--complete" ]; then complete=1; shift; fi
list="${1:?usage: check-surface.sh [--complete] <surface-list> <object>...}"
shift
[ "$#" -gt 0 ] || { echo "no objects given" >&2; exit 2; }
spec="$(grep -vE '^[[:space:]]*(#|$)' "$list" | sort -u)"
# T and W are functions; R, D, B and S are data. All are exported and all are
# therefore part of the surface. A checker that inspected only text would report
# a conforming surface for an implementation that had added a capability word.
#
# A leading underscore is removed before comparison. Some systems prefix C
# symbols with one and some do not, and a checker that ignored the difference
# would find no names at all on one of them — and would report that as success,
# because an empty surface contains nothing unspecified. The defect was found by
# writing a second implementation, which is what a second implementation is for.
# THE SYMBOL READER IS NAMED, BECAUSE `nm` DOES NOT READ EVERY OBJECT THIS
# ECOSYSTEM PRODUCES.
#
# A wasm object is not an ELF, and the host's binutils `nm` reports
# "file format not recognized" for one. The reader that does read it ships with
# the toolchain that produced it -- `llvm-nm` inside the emsdk payload -- so it
# is named in the environment rather than assumed, in the same way the runner
# and the features are. The default is unchanged, so every existing caller is.
#
# This is the same class of defect as the leading-underscore note above, found
# the same way: by writing another implementation.
NM="${NM:-nm}"
found="$($NM --defined-only "$@" \
| awk '$2=="T"||$2=="W"||$2=="R"||$2=="D"||$2=="B"||$2=="S"{print $3}' \
| sed 's/^_//' \
| grep '^kal_' | sort -u || true)"
# An empty surface is never a conforming one. Every implementation exports
# something, so nothing found means the objects were wrong or the symbols were
# not recognised, and reporting success would conceal both.
if [ -z "$found" ]; then
echo "no exported name beginning with kal_ was found; the objects or the symbol format are wrong" >&2
exit 1
fi
status=0
while read -r name; do
[ -n "$name" ] || continue
# ⚠️⚠️ A HERE-STRING AND NOT A PIPE, AND THE DIFFERENCE IS A FALSE RED.
#
# `grep -q' exits at the FIRST match, which closes the pipe under a `printf'
# that may still be writing; the printf then dies of SIGPIPE, and `pipefail'
# at the top of this file makes the whole pipeline report failure. The `!'
# inverts that into "not in the specification" --- for a name that IS in it.
#
# ⚠️ It depends on whether the list fits in the pipe buffer before grep
# exits, so it fires occasionally and passes on a re-run, which is the worst
# shape a check can have: openkal-macos reported `kal_stdin' missing on one
# run and clean on the next with no change between them. The name is the
# twenty-seventh line of SURFACE.txt --- an early match is exactly the case
# that leaves the most left to write.
if ! grep -qxF -- "$name" <<< "$spec"; then
echo "exported name is not in the specification: $name" >&2
status=1
fi
done <<< "$found"
# --complete, and what it means once the specification has an optional tier.
#
# It used to mean "every name in the list is exported", which is the claim of an
# implementation that provides the whole specification. That claim stopped being
# the right one the moment an interface became optional: an implementation that
# declines an optional interface is not incomplete, and clause 6.1 says so.
#
# So --complete is checked GROUP BY GROUP, which is the rule SURFACE.txt's own
# header already states --- "an implementation provides an interface in whole or
# not at all, so the absence of a group below denotes an interface the
# implementation does not provide and is not a deviation". A group none of whose
# names is exported is an interface not provided. A group SOME of whose names
# are exported is the thing this check exists to catch: half an interface.
if [ "$complete" -eq 1 ]; then
group=''; want=''
check_group() {
[ -n "$group" ] && [ -n "$want" ] || return 0
local present=0 absent=0 missing=''
while read -r name; do
[ -n "$name" ] || continue
# A here-string for the reason given above. Here the same SIGPIPE
# would UNDER-COUNT what is exported, so a complete interface would
# be reported as half of one.
if grep -qxF -- "$name" <<< "$found"; then present=$((present+1))
else absent=$((absent+1)); missing="$missing $name"; fi
done <<< "$want"
if [ "$present" -gt 0 ] && [ "$absent" -gt 0 ]; then
echo "$group is provided in part: $absent of $((present+absent)) names are not exported --" >&2
for m in $missing; do echo " $m" >&2; done
status=1
fi
}
while IFS= read -r line; do
case "$line" in
'# openkal.'*) check_group; group="${line#\# }"; want='' ;;
'#'*|'') ;;
*) want="$want$line
" ;;
esac
done < "$list"
check_group
fi
if [ "$status" -eq 0 ]; then
n=$(printf '%s\n' "$found" | grep -c .)
if [ "$complete" -eq 1 ]; then
echo "exported surface is complete and conforms: $n name(s)"
else
echo "exported surface conforms: $n name(s)"
fi
fi
exit "$status"