wmaker-check: rewrote parsing of structure fields for callback checker
[wmaker-crm.git] / script / check-wmaker-loaddef-callbacks.sh
blob57d4cda9674c1d0061fbded352d7574f102dccb0
1 #!/bin/sh
2 ###########################################################################
4 # Window Maker window manager
6 # Copyright (c) 2015 Christophe CURIS
7 # Copyright (c) 2015 Window Maker Team
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along
20 # with this program. If not, see <http://www.gnu.org/licenses/>.
22 ###########################################################################
24 # check-wmaker-loaddef-callbacks.sh:
25 # Compare the type defined in a variable against the type use for the
26 # associated call-back function.
28 # To load the configuration file, Window Maker is using a list of the known
29 # keywords in a structure with the name of the keyword, the call-back
30 # function that converts the string (from file) into the appropriate type,
31 # and a pointer to the variable where the result is saved.
33 # Because the structure requires a little bit of genericity to be usefull,
34 # it is not possible to provide the C compiler with the information to
35 # check that the variable from the pointer has the same type as what the
36 # conversion call-back assumes, so this script does that check for us.
38 # Unfortunately, the script cannot be completely generic and smart, but
39 # it still tries to be, despite being made explicitely for the case of the
40 # structures "staticOptionList" and "optionList" from Window Maker's source
41 # file "src/defaults.c"
43 ###########################################################################
45 # For portability, we stick to the same sh+awk constraint as Autotools to
46 # limit problems, see for example:
47 # http://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Portable-Shell.html
49 ###########################################################################
51 # Report an error on stderr and exit with status 2 to tell make that we could
52 # not do what we were asked
53 arg_error() {
54 echo "`basename $0`: $@" >&2
55 exit 2
58 # print help and exit with success status
59 print_help() {
60 echo "$0: check variable type against call-back expectation for WMaker's defaults.c"
61 echo "Usage: $0 options..."
62 echo "valid options are:"
63 echo " --callback \"name=type\" : specify that function 'name' expects a variable of 'type'"
64 echo " --field-callback idx : index (from 1) of the callback function in the struct"
65 echo " --field-value-ptr idx : index (from 1) of the pointer-to-value in the struct"
66 echo " --source file : C source file with the array to check"
67 echo " --structure name : name of the variable with the array of struct to check"
68 echo " --struct-def name=file : specify to get definition of struct 'name' from 'file'"
69 exit 0
72 # Extract command line arguments
73 while [ $# -gt 0 ]; do
74 case $1 in
75 --callback)
76 shift
77 deflist="$1,"
78 while [ -n "$deflist" ]; do
79 name_and_type=`echo "$deflist" | cut -d, -f1 | sed -e 's/^[ \t]*//;s/[ \t]*$//' `
80 deflist=`echo "$deflist" | cut -d, -f2-`
81 echo "$name_and_type" | grep '^[A-Za-z][A-Za-z_0-9]*[ \t]*=[^=][^=]*$' > /dev/null || \
82 arg_error "invalid callback function type specification '$name_and_type' (options: --callback)"
83 name=`echo "$name_and_type" | sed -e 's/=.*$// ; s/[ \t]//g' `
84 type=`echo "$name_and_type" | sed -e 's/^[^=]*=// ; s/^[ \t]*// ; s/[ \t][ \t]*/ /g' `
85 awk_callback_types="$awk_callback_types
86 callback[\"$name\"] = \"$type\";"
87 done
90 --field-callback)
91 shift
92 [ -z "$field_callback" ] || arg_error "field number specified more than once (option: --field-callback)"
93 field_callback="$1"
96 --field-value-ptr)
97 shift
98 [ -z "$field_value" ] || arg_error "field number specified more than once (option: --field-value-ptr)"
99 field_value="$1"
102 --source)
103 shift
104 [ -z "$source_file" ] || arg_error "only 1 source file can be used (option: --source)"
105 source_file="$1"
108 --structure)
109 shift
110 [ -z "$struct_name" ] || arg_error "only 1 structure can be checked (option: --structure)"
111 struct_name="$1"
114 --struct-def)
115 shift
116 echo "$1" | grep '^[A-Za-z][A-Z_a-z0-9]*=' > /dev/null || arg_error "invalid syntax in \"$1\" for --struct-def"
117 [ -r "`echo $1 | sed -e 's/^[^=]*=//' `" ] || arg_error "file not readable in struct-def \"$1\""
118 list_struct_def="$list_struct_def
122 -h|-help|--help) print_help ;;
123 -*) arg_error "unknow option '$1'" ;;
126 arg_error "argument '$1' is not understood"
128 esac
129 shift
130 done
132 # Check consistency of command-line
133 [ -z "$source_file" ] && arg_error "no source file given (option: --source)"
134 [ -z "$struct_name" ] && arg_error "no variable name given for the array to check (option: --structure)"
135 [ -z "$field_value" ] && arg_error "index of the value pointer in the struct no specified (option: --field-value-ptr)"
136 [ -z "$field_callback" ] && arg_error "index of the call-back in the struct no specified (option: --field-callback)"
138 echo "$field_value" | grep '^[1-9][0-9]*$' > /dev/null || arg_error "invalid index for the value pointer, expecting a number (option: --field-value-ptr)"
139 echo "$field_callback" | grep '^[1-9][0-9]*$' > /dev/null || arg_error "invalid index for the call-back function, expecting a number (option: --field-callback)"
141 ###########################################################################
143 # This AWK script is extracting the types associated with the field members
144 # from a specific structure defined in the parsed C source file
145 awk_extract_struct='
146 # Parse all the lines until the end of current structure is found
147 function parse_structure(prefix) {
148 while (getline) {
150 # Discard C comments, with support for multi-line comments
151 while (1) {
152 idx = index($0, "/*");
153 if (idx == 0) { break; }
155 comment = substr($0, idx+2);
156 $0 = substr($0, 1, idx-1);
157 while (1) {
158 idx = index(comment, "*/");
159 if (idx > 0) { break; }
160 getline comment;
162 $0 = $0 substr(comment, idx+2);
165 # skip line that define nothing interresting
166 gsub(/^[ \t]+/, "");
167 if (/^$/) { continue; }
168 if (/^#/) { continue; }
169 gsub(/[ \t]+$/, "");
171 # end of current structure: extract the name and return it
172 if (/^[ \t]*\}/) {
173 gsub(/^\}[ \t]*/, "");
174 name = $0;
175 gsub(/[ \t]*;.*$/, "", name);
176 gsub(/^[^;]*;/, "");
177 return name;
180 # Handle structure inside structure
181 if (/^(struct|union)[ \t]+\{/) {
182 name = parse_structure(prefix ",!");
184 # Update the name tracking the content of this struct to contain the name
185 # of the struct itself
186 match_prefix = "^" prefix ",!:";
187 for (var_id in member) {
188 if (var_id !~ match_prefix) { continue; }
189 new_id = var_id;
190 gsub(/,!:/, ":" name ".", new_id);
191 member[new_id] = member[var_id];
192 delete member[var_id];
194 continue;
197 if (!/;$/) {
198 print "Warning: line " FILENAME ":" NR " not understood inside struct" > "/dev/stderr";
199 continue;
201 gsub(/;$/, "");
203 # Skip the lines that define a bit-field because they cannot be safely
204 # pointed to anyway
205 if (/:/) { continue; }
207 separate_type_and_names();
209 # In some rare case we cannot extract the name, that is likely a function pointer type
210 if (type == "") { continue; }
212 # Save this information in an array
213 for (i = 1; i <= nb_names; i++) {
214 # If it is an array, push that information into the type
215 idx = index(name_list[i], "[");
216 if (idx > 0) {
217 member[prefix ":" substr(name_list[i], 1, idx-1) ] = type substr(name_list[i], idx);
218 } else {
219 member[prefix ":" name_list[i] ] = type;
225 # Separate the declaration of an member of the struct: its type and the list of names
226 # The result is returned through variables "name_list + nb_names" and "type"
227 function separate_type_and_names() {
228 # Separate by names first
229 nb_names = split($0, name_list, /[ \t]*,[ \t]*/);
231 # Separate the type from the 1st name
232 if (name_list[1] ~ /\]$/) {
233 idx = index(name_list[1], "[") - 1;
234 } else {
235 idx = length(name_list[1]);
237 while (substr(name_list[1], idx, 1) ~ /[A-Za-z_0-9]/) { idx--; }
239 type = substr(name_list[1], 1, idx);
240 name_list[1] = substr(name_list[1], idx + 1);
242 if (type ~ /\(/) {
243 # If therese is a parenthesis in the type, that means we are parsing a function pointer
244 # declaration. This is not supported at current time (not needed), so we silently ignore
245 type = "";
246 return;
249 # Remove size information from array declarations
250 for (i in name_list) {
251 gsub(/\[[^\]]+\]/, "[]", name_list[i]);
254 # Parse the type to make it into a "standard" format
255 gsub(/[ \t]+$/, "", type);
256 nb_elem = split(type, type_list, /[ \t]+/);
257 type = "";
258 for (i = 1; i <= nb_elem; i++) {
259 if (type_list[i] == "signed" || type_list[i] == "unsigned") {
260 # The sign information is not a problem for pointer compatibility, so we do not keep it
261 continue;
263 if (type_list[i] ~ /^\*+$/) {
264 # If we have a pointer mark by itself, we glue it to the previous keyword
265 type = type type_list[i];
266 } else {
267 type = type " " type_list[i];
270 gsub(/^ /, "", type);
271 if (type ~ /^\*/ || type == "") {
272 # We have a signed/unsigned without explicit "int" specification, add it now
273 type = "int" type;
277 # The name of the variable is at the end, so find all structure definition
278 /^([a-z]*[ \t][ \t]*)*struct[ \t]/ {
280 # Discard all words to find the first ; or {
281 gsub(/^([A-Za-z_0-9]*[ \t][ \t]*)+/, "");
283 # If not an { it is probably not what we are looking for
284 if (/^[^\{]/) { next; }
286 # Read everything until we find the end of the structure; we assume a
287 # definition is limited to one line
288 name = parse_structure("@");
290 # If the name is what we expect, generate the appropriate stuff
291 if (name == expected_name) {
292 struct_found++;
293 for (i in member) {
294 $0 = i;
295 gsub(/^@:/, expected_name ".");
296 print " variable[\"" $0 "\"] = \"" member[i] "\";";
300 # Purge the array to not mix fields between the different structures
301 for (i in member) { delete member[i]; }
304 # Check that everything was ok
305 END {
306 if (struct_found == 0) {
307 print "Error: structure \"" expected_name "\" was not found in " FILENAME > "/dev/stderr";
308 exit 1;
309 } else if (struct_found > 1) {
310 print "Error: structure \"" expected_name "\" was defined more than once in " FILENAME > "/dev/stderr";
311 exit 1;
315 # Do not print anything else than what is generated while parsing structures
319 # Extract the information for all the structures specified on the command line
320 awk_array_types=`echo "$list_struct_def" | while
321 IFS="=" read name file
323 [ -z "$name" ] && continue
325 awk_script="
326 BEGIN {
327 struct_found = 0;
328 expected_name = \"$name\";
330 $awk_extract_struct"
332 echo " # $file"
334 awk "$awk_script" "$file"
335 [ $? -ne 0 ] && exit $?
337 done`
339 ###########################################################################
341 # Parse the source file to extract the list of call-back functions that are
342 # used; take the opportunity to extract information about the variable
343 # being pointed to now to avoid re-parsing too many times the file
344 awk_check_callbacks='
345 # Search the final } for the current element in the array, then split the
346 # elements into the array "entry_elements" and remove that content from $0
347 function get_array_element_and_split() {
348 nb_elements = 1;
349 entry_elements[nb_elements] = "";
351 $0 = substr($0, 2);
352 count_braces = 1;
353 while (count_braces > 0) {
354 char = substr($0, 1, 1);
355 $0 = substr($0, 2);
356 if (char == "{") {
357 count_braces++;
358 } else if (char == "}") {
359 count_braces--;
360 } else if (char ~ /[ \t]/) {
361 # Just discard
362 } else if (char == ",") {
363 if (count_braces == 1) { nb_elements++; entry_elements[nb_elements] = ""; }
364 } else if (char == "\"") {
365 entry_elements[nb_elements] = entry_elements[nb_elements] extract_string_to_element();
366 } else if (char == "/") {
367 if (substr($0, 1, 1) == "/") {
368 getline;
369 while (/^#/) { getline; }
370 } else if (substr($0, 1, 1) == "*") {
371 $0 = substr($0, 2);
372 skip_long_comment();
373 } else {
374 entry_elements[nb_elements] = entry_elements[nb_elements] char;
376 } else if (char == "") {
377 getline;
378 while (/^#/) { print "skip: " $0; getline; }
379 } else {
380 entry_elements[nb_elements] = entry_elements[nb_elements] char;
385 # When a string enclosed in "" is encountered as part of the elements of the
386 # array, it requires special treatment, not to extract the information (we are
387 # unlikely to care about these fields) but to avoid mis-parsing the fields
388 function extract_string_to_element() {
389 content = "\"";
390 while (1) {
391 char = substr($0, 1, 1);
392 $0 = substr($0, 2);
393 if (char == "\\") {
394 content = content char substr($0, 1, 1);
395 $0 = substr($0, 2);
396 } else if (char == "\"") {
397 break;
398 } else if (char == "") {
399 getline;
400 } else {
401 content = content char;
404 return content "\"";
407 # Wherever a long C comment (/* comment */) is encountered, it is discarded
408 function skip_long_comment() {
409 while (1) {
410 idx = index($0, "*/");
411 if (idx > 0) {
412 $0 = substr($0, idx + 2);
413 break;
415 getline;
419 # Search for the definition of an array with the good name
420 /^[ \t]*([A-Z_a-z][A-Z_a-z0-9*]*[ \t]+)+'$struct_name'[ \t]*\[\][ \t]*=[ \t]*\{/ {
421 struct_found++;
423 $0 = substr($0, index($0, "{") + 1);
425 # Parse all the elements of the array
426 while (1) {
428 # Search for start of an element
429 while (1) {
430 gsub(/^[ \t]+/, "");
431 if (substr($0, 1, 1) == "{") { break; }
432 if (substr($0, 1, 1) == "}") { break; }
433 if (substr($0, 1, 1) == "#") { getline; continue; }
434 if ($0 == "") { getline; continue; }
436 # Remove comments
437 if (substr($0, 1, 2) == "//") { getline; continue; }
438 if (substr($0, 1, 2) == "/*") {
439 $0 = substr($0, 3);
440 skip_long_comment();
441 } else {
442 print "Warning: line " NR " not understood in " FILENAME ", skipped" > "/dev/stderr";
443 getline;
447 # Did we find the end of the array?
448 if (substr($0, 1, 1) == "}") { break; }
450 # Grab the whole content of the entry
451 entry_start_line = NR;
452 get_array_element_and_split();
453 gsub(/^[ \t]*,/, "");
455 # Extract the 2 fields we are interrested in
456 if ((entry_elements[src_fvalue] != "NULL") && (entry_elements[src_ffunct] != "NULL")) {
458 if (substr(entry_elements[src_fvalue], 1, 1) == "&") {
459 entry_elements[src_fvalue] = substr(entry_elements[src_fvalue], 2);
460 } else {
461 print "Warning: value field used in entry at line " entry_start_line " does not looke like a pointer" > "/dev/stderr";
464 if (variable[entry_elements[src_fvalue]] == "") {
465 print "Warning: type is not known for \"" entry_elements[src_fvalue] "\" at line " entry_start_line ", cannot check" > "/dev/stderr";
466 } else if (callback[entry_elements[src_ffunct]] == "") {
467 print "Error: expected type for callback function \"" entry_elements[src_ffunct] "\" is not known, from " FILENAME ":" entry_start_line > "/dev/stderr";
468 error_count++;
469 } else if (callback[entry_elements[src_ffunct]] != variable[entry_elements[src_fvalue]]) {
470 print "Error: type mismatch between function and variable in " FILENAME ":" entry_start_line > "/dev/stderr";
471 print " Function: " entry_elements[src_ffunct] " expects \"" callback[entry_elements[src_ffunct]] "\"" > "/dev/stderr";
472 print " Variable: " entry_elements[src_fvalue] " has type \"" variable[entry_elements[src_fvalue]] "\"" > "/dev/stderr";
473 error_count++;
480 # Final checks
481 END {
482 if (error_count > 0) { exit 1; }
483 if (struct_found == 0) {
484 print "Error: structure \"'$struct_name'\" was not found in " FILENAME > "/dev/stderr";
485 exit 1;
486 } else if (struct_found > 1) {
487 print "Error: structure \"'$struct_name'\" was defined more than once in " FILENAME > "/dev/stderr";
488 exit 1;
492 # Do not print anything else than what is generated while parsing the structure
496 awk_script="BEGIN {
497 $awk_array_types
498 $awk_callback_types
500 # Info on structure to be checked
501 src_fvalue = $field_value;
502 src_ffunct = $field_callback;
504 # For checks
505 struct_found = 0;
506 error_count = 0;
508 $awk_check_callbacks"
510 awk "$awk_script" "$source_file" || exit $?