| @@ -1,14 +1,14 @@ | | | @@ -1,14 +1,14 @@ |
1 | /* $NetBSD: check-portability.c,v 1.6 2020/03/12 19:26:17 rillig Exp $ */ | | 1 | /* $NetBSD: check-portability.c,v 1.7 2020/03/12 19:45:06 rillig Exp $ */ |
2 | | | 2 | |
3 | /* | | 3 | /* |
4 | Copyright (c) 2020 Roland Illig | | 4 | Copyright (c) 2020 Roland Illig |
5 | All rights reserved. | | 5 | All rights reserved. |
6 | | | 6 | |
7 | Redistribution and use in source and binary forms, with or without | | 7 | Redistribution and use in source and binary forms, with or without |
8 | modification, are permitted provided that the following conditions | | 8 | modification, are permitted provided that the following conditions |
9 | are met: | | 9 | are met: |
10 | | | 10 | |
11 | 1. Redistributions of source code must retain the above copyright | | 11 | 1. Redistributions of source code must retain the above copyright |
12 | notice, this list of conditions and the following disclaimer. | | 12 | notice, this list of conditions and the following disclaimer. |
13 | 2. Redistributions in binary form must reproduce the above copyright | | 13 | 2. Redistributions in binary form must reproduce the above copyright |
14 | notice, this list of conditions and the following disclaimer in the | | 14 | notice, this list of conditions and the following disclaimer in the |
| @@ -228,51 +228,69 @@ static bool | | | @@ -228,51 +228,69 @@ static bool |
228 | str_read_text_line(str *s, FILE *f) | | 228 | str_read_text_line(str *s, FILE *f) |
229 | { | | 229 | { |
230 | int c; | | 230 | int c; |
231 | | | 231 | |
232 | s->len = 0; | | 232 | s->len = 0; |
233 | while ((c = fgetc(f)) > '\0' && c != '\n' && c != '\r') { | | 233 | while ((c = fgetc(f)) > '\0' && c != '\n' && c != '\r') { |
234 | str_append_char(s, (char) c); | | 234 | str_append_char(s, (char) c); |
235 | } | | 235 | } |
236 | assert(c != '\0'); | | 236 | assert(c != '\0'); |
237 | return c != EOF; | | 237 | return c != EOF; |
238 | } | | 238 | } |
239 | | | 239 | |
240 | typedef enum { | | 240 | typedef enum { |
| | | 241 | W_how_to_fix, |
241 | W_dollar_random, | | 242 | W_dollar_random, |
242 | W_test_eqeq, | | 243 | W_test_eqeq, |
243 | W_double_bracket | | 244 | W_double_bracket |
244 | } warning_kind; | | 245 | } warning_kind; |
245 | | | 246 | |
246 | static unsigned long explained = 0; | | 247 | static unsigned long explained = 0; |
247 | | | 248 | |
248 | static void | | 249 | static void |
249 | explain(warning_kind warning, ...) | | 250 | explain(warning_kind warning, ...) |
250 | { | | 251 | { |
251 | unsigned long mask = 1UL << warning; | | 252 | unsigned long mask = 1UL << warning; |
252 | if ((explained & mask) != 0) | | 253 | if ((explained & mask) != 0) |
253 | return; | | 254 | return; |
254 | explained |= mask; | | 255 | explained |= mask; |
255 | | | 256 | |
256 | va_list args; | | 257 | va_list args; |
257 | va_start(args, warning); | | 258 | va_start(args, warning); |
258 | printf("\n"); | | 259 | printf("\n"); |
259 | const char *line; | | 260 | const char *line; |
260 | while ((line = va_arg(args, const char *)) != nullptr) | | 261 | while ((line = va_arg(args, const char *)) != nullptr) |
261 | printf("%s%s\n", line[0] == '\0' ? "" : "\t", line); | | 262 | printf("%s%s\n", line[0] == '\0' ? "" : "\t", line); |
262 | printf("\n"); | | 263 | printf("\n"); |
263 | va_end(args); | | 264 | va_end(args); |
264 | } | | 265 | } |
265 | | | 266 | |
| | | 267 | static void |
| | | 268 | explain_how_to_fix(void) |
| | | 269 | { |
| | | 270 | explain( |
| | | 271 | W_how_to_fix, |
| | | 272 | "To fix this message, decide whether this file is necessary", |
| | | 273 | "for the package to build. Then choose any of these variants:", |
| | | 274 | "", |
| | | 275 | "1. Add a patch for the file", |
| | | 276 | " (see https://www.netbsd.org/docs/pkgsrc/pkgsrc.html#components.patches)", |
| | | 277 | "2. Add a SUBST block for the file to the package Makefile", |
| | | 278 | " (see mk/subst.mk)", |
| | | 279 | "3. Add CHECK_PORTABILITY_SKIP+= shell/glob to the package Makefile", |
| | | 280 | " (see mk/check/check-portability.mk)", |
| | | 281 | nullptr); |
| | | 282 | } |
| | | 283 | |
266 | static size_t | | 284 | static size_t |
267 | index_opening_bracket(cstr s) | | 285 | index_opening_bracket(cstr s) |
268 | { | | 286 | { |
269 | size_t index = cstr_index(s, CSTR("[[")); | | 287 | size_t index = cstr_index(s, CSTR("[[")); |
270 | if (index == npos) | | 288 | if (index == npos) |
271 | return npos; | | 289 | return npos; |
272 | if (index > 0 && !is_hspace(s.data[index - 1])) | | 290 | if (index > 0 && !is_hspace(s.data[index - 1])) |
273 | return npos; | | 291 | return npos; |
274 | if (index + 2 < s.len && !is_hspace(s.data[index + 2])) | | 292 | if (index + 2 < s.len && !is_hspace(s.data[index + 2])) |
275 | return npos; | | 293 | return npos; |
276 | return index; | | 294 | return index; |
277 | } | | 295 | } |
278 | | | 296 | |
| @@ -318,26 +336,27 @@ checkline_sh_double_brackets(cstr filena | | | @@ -318,26 +336,27 @@ checkline_sh_double_brackets(cstr filena |
318 | cstr_charptr(filename), lineno, opening_index + 1, | | 336 | cstr_charptr(filename), lineno, opening_index + 1, |
319 | cstr_charptr(line)); | | 337 | cstr_charptr(line)); |
320 | nerrors++; | | 338 | nerrors++; |
321 | explain( | | 339 | explain( |
322 | W_double_bracket, | | 340 | W_double_bracket, |
323 | "The keyword [[ is only available in bash, not in other shells.", | | 341 | "The keyword [[ is only available in bash, not in other shells.", |
324 | "Since it is typically used inside an if statement, if that", | | 342 | "Since it is typically used inside an if statement, if that", |
325 | "command is missing, it is interpreted as a \"no\".", | | 343 | "command is missing, it is interpreted as a \"no\".", |
326 | "", | | 344 | "", |
327 | "An error message of the form \"[[: command not found\"", | | 345 | "An error message of the form \"[[: command not found\"", |
328 | "is logged, but that is easy to overlook in the large", | | 346 | "is logged, but that is easy to overlook in the large", |
329 | "output of the build process.", | | 347 | "output of the build process.", |
330 | nullptr); | | 348 | nullptr); |
| | | 349 | explain_how_to_fix(); |
331 | } | | 350 | } |
332 | | | 351 | |
333 | // Check for $RANDOM, which is specific to ksh and bash. | | 352 | // Check for $RANDOM, which is specific to ksh and bash. |
334 | static void | | 353 | static void |
335 | checkline_sh_dollar_random(cstr filename, size_t lineno, cstr line) | | 354 | checkline_sh_dollar_random(cstr filename, size_t lineno, cstr line) |
336 | { | | 355 | { |
337 | // Note: This code does not find _all_ instances of | | 356 | // Note: This code does not find _all_ instances of |
338 | // unportable code. If a single line contains an unsafe and | | 357 | // unportable code. If a single line contains an unsafe and |
339 | // a safe usage of $RANDOM, it will pass the test. | | 358 | // a safe usage of $RANDOM, it will pass the test. |
340 | if (is_shell_comment(line)) | | 359 | if (is_shell_comment(line)) |
341 | return; | | 360 | return; |
342 | if (!cstr_contains(line, CSTR("$RANDOM"))) | | 361 | if (!cstr_contains(line, CSTR("$RANDOM"))) |
343 | return; | | 362 | return; |
| @@ -354,26 +373,27 @@ checkline_sh_dollar_random(cstr filename | | | @@ -354,26 +373,27 @@ checkline_sh_dollar_random(cstr filename |
354 | if (idx != npos && idx + 7 < line.len && is_alnum(line.data[idx + 7])) | | 373 | if (idx != npos && idx + 7 < line.len && is_alnum(line.data[idx + 7])) |
355 | return; | | 374 | return; |
356 | | | 375 | |
357 | printf("%s:%zu:%zu: $RANDOM: %s\n", | | 376 | printf("%s:%zu:%zu: $RANDOM: %s\n", |
358 | cstr_charptr(filename), lineno, idx + 1, | | 377 | cstr_charptr(filename), lineno, idx + 1, |
359 | cstr_charptr(line)); | | 378 | cstr_charptr(line)); |
360 | explain( | | 379 | explain( |
361 | W_dollar_random, | | 380 | W_dollar_random, |
362 | "The variable $RANDOM is not required for a POSIX-conforming shell, and", | | 381 | "The variable $RANDOM is not required for a POSIX-conforming shell, and", |
363 | "many implementations of /bin/sh do not support it. It should therefore", | | 382 | "many implementations of /bin/sh do not support it. It should therefore", |
364 | "not be used in shell programs that are meant to be portable across a", | | 383 | "not be used in shell programs that are meant to be portable across a", |
365 | "large number of POSIX-like systems.", | | 384 | "large number of POSIX-like systems.", |
366 | nullptr); | | 385 | nullptr); |
| | | 386 | explain_how_to_fix(); |
367 | } | | 387 | } |
368 | | | 388 | |
369 | typedef void (*foreach_3_fields_cb)(cstr f1, cstr f2, cstr f3, void *actiondata); | | 389 | typedef void (*foreach_3_fields_cb)(cstr f1, cstr f2, cstr f3, void *actiondata); |
370 | | | 390 | |
371 | static void | | 391 | static void |
372 | foreach_3_fields_in_line(cstr line, foreach_3_fields_cb action, void *actiondata) | | 392 | foreach_3_fields_in_line(cstr line, foreach_3_fields_cb action, void *actiondata) |
373 | { | | 393 | { |
374 | size_t idx = 0; | | 394 | size_t idx = 0; |
375 | cstr f1 = cstr_next_field(line, &idx); | | 395 | cstr f1 = cstr_next_field(line, &idx); |
376 | cstr f2 = cstr_next_field(line, &idx); | | 396 | cstr f2 = cstr_next_field(line, &idx); |
377 | cstr f3 = cstr_next_field(line, &idx); | | 397 | cstr f3 = cstr_next_field(line, &idx); |
378 | | | 398 | |
379 | while (f3.len > 0) { | | 399 | while (f3.len > 0) { |
| @@ -403,33 +423,28 @@ checkline_sh_test_eqeq_action(cstr f1, c | | | @@ -403,33 +423,28 @@ checkline_sh_test_eqeq_action(cstr f1, c |
403 | "%s:%zu:%zu: found test ... == ...: %s\n", | | 423 | "%s:%zu:%zu: found test ... == ...: %s\n", |
404 | cstr_charptr(ad->filename), ad->lineno, | | 424 | cstr_charptr(ad->filename), ad->lineno, |
405 | (size_t) (f3.data - ad->line.data), | | 425 | (size_t) (f3.data - ad->line.data), |
406 | cstr_charptr(ad->line)); | | 426 | cstr_charptr(ad->line)); |
407 | explain( | | 427 | explain( |
408 | W_test_eqeq, | | 428 | W_test_eqeq, |
409 | "The \"test\" command, as well as the \"[\" command, are not required to know", | | 429 | "The \"test\" command, as well as the \"[\" command, are not required to know", |
410 | "the \"==\" operator. Only a few implementations like bash and some", | | 430 | "the \"==\" operator. Only a few implementations like bash and some", |
411 | "versions of ksh support it.", | | 431 | "versions of ksh support it.", |
412 | "", | | 432 | "", |
413 | "When you run \"test foo == foo\" on a platform that does not support the", | | 433 | "When you run \"test foo == foo\" on a platform that does not support the", |
414 | "\"==\" operator, the result will be \"false\" instead of \"true\". This can", | | 434 | "\"==\" operator, the result will be \"false\" instead of \"true\". This can", |
415 | "lead to unexpected behavior.", | | 435 | "lead to unexpected behavior.", |
416 | "", | | | |
417 | "There are two ways to fix this error message. If the file that contains", | | | |
418 | "the \"test ==\" is needed for building the package, you should create a", | | | |
419 | "patch for it, replacing the \"==\" operator with \"=\". If the file is not", | | | |
420 | "needed, add its name to the CHECK_PORTABILITY_SKIP variable in the", | | | |
421 | "package Makefile.", | | | |
422 | nullptr); | | 436 | nullptr); |
| | | 437 | explain_how_to_fix(); |
423 | } | | 438 | } |
424 | | | 439 | |
425 | static void | | 440 | static void |
426 | checkline_sh_test_eqeq(cstr filename, size_t lineno, cstr line) | | 441 | checkline_sh_test_eqeq(cstr filename, size_t lineno, cstr line) |
427 | { | | 442 | { |
428 | if (is_shell_comment(line)) | | 443 | if (is_shell_comment(line)) |
429 | return; | | 444 | return; |
430 | | | 445 | |
431 | struct checkline_sh_test_eqeq_actiondata ad = { filename, lineno, line }; | | 446 | struct checkline_sh_test_eqeq_actiondata ad = { filename, lineno, line }; |
432 | foreach_3_fields_in_line(line, checkline_sh_test_eqeq_action, &ad); | | 447 | foreach_3_fields_in_line(line, checkline_sh_test_eqeq_action, &ad); |
433 | } | | 448 | } |
434 | | | 449 | |
435 | static bool | | 450 | static bool |