1 /* $NetBSD: ckgetopt.c,v 1.16 2022/05/20 21:18:55 rillig Exp $ */ 2 3 /*- 4 * Copyright (c) 2021 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Roland Illig <rillig@NetBSD.org>. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #if HAVE_NBTOOL_CONFIG_H 33 #include "nbtool_config.h" 34 #endif 35 36 #include <sys/cdefs.h> 37 #if defined(__RCSID) 38 __RCSID("$NetBSD: ckgetopt.c,v 1.16 2022/05/20 21:18:55 rillig Exp $"); 39 #endif 40 41 #include <stdbool.h> 42 #include <stdlib.h> 43 #include <string.h> 44 45 #include "lint1.h" 46 47 /* 48 * In a typical while loop for parsing getopt options, ensure that each 49 * option from the options string is handled, and that each handled option 50 * is listed in the options string. 51 */ 52 53 static struct { 54 /* 55 * 0 means outside a while loop with a getopt call. 56 * 1 means directly inside a while loop with a getopt call. 57 * > 1 means in a nested while loop; this is used for finishing the 58 * check at the correct point. 59 */ 60 int while_level; 61 62 /* 63 * The options string from the getopt call. Whenever an option is 64 * handled by a case label, it is set to ' '. In the end, only ' ' 65 * and ':' should remain. 66 */ 67 pos_t options_pos; 68 char *options; 69 70 /* 71 * The nesting level of switch statements, is only modified if 72 * while_level > 0. Only the case labels at switch_level == 1 are 73 * relevant, all nested case labels are ignored. 74 */ 75 int switch_level; 76 } ck; 77 78 #define NEED(cond) \ 79 do { \ 80 if (!(cond)) \ 81 return false; \ 82 } while (false) 83 84 /* Return whether tn has the form 'getopt(argc, argv, "literal") != -1'. */ 85 static bool 86 is_getopt_condition(const tnode_t *tn, char **out_options) 87 { 88 const tnode_t *call, *last_arg; 89 90 NEED(tn != NULL); 91 NEED(tn->tn_op == NE); 92 NEED(tn->tn_left->tn_op == ASSIGN); 93 94 call = tn->tn_left->tn_right; 95 NEED(call->tn_op == CALL); 96 NEED(call->tn_left->tn_op == ADDR); 97 NEED(call->tn_left->tn_left->tn_op == NAME); 98 NEED(strcmp(call->tn_left->tn_left->tn_sym->s_name, "getopt") == 0); 99 100 NEED(call->tn_right->tn_op == PUSH); 101 102 last_arg = call->tn_right->tn_left; 103 NEED(last_arg->tn_op == CVT); 104 NEED(last_arg->tn_left->tn_op == ADDR); 105 NEED(last_arg->tn_left->tn_left->tn_op == STRING); 106 NEED(last_arg->tn_left->tn_left->tn_string->st_char); 107 108 *out_options = xstrdup(last_arg->tn_left->tn_left->tn_string->st_mem); 109 return true; 110 } 111 112 static void 113 check_unlisted_option(char opt) 114 { 115 char *optptr; 116 117 lint_assert(ck.options != NULL); 118 119 if (opt == ':' && ck.options[0] != ':') 120 goto warn; 121 122 optptr = strchr(ck.options, opt); 123 if (optptr != NULL) 124 *optptr = ' '; 125 else if (opt != '?') { 126 warn: 127 /* option '%c' should be listed in the options string */ 128 warning(339, opt); 129 } 130 } 131 132 static void 133 check_unhandled_option(void) 134 { 135 const char *opt; 136 137 lint_assert(ck.options != NULL); 138 139 for (opt = ck.options; *opt != '\0'; opt++) { 140 if (*opt == ' ' || *opt == ':') 141 continue; 142 143 /* option '%c' should be handled in the switch */ 144 warning_at(338, &ck.options_pos, *opt); 145 } 146 } 147 148 149 void 150 check_getopt_begin_while(const tnode_t *tn) 151 { 152 if (ck.while_level == 0) { 153 if (!is_getopt_condition(tn, &ck.options)) 154 return; 155 ck.options_pos = curr_pos; 156 } 157 ck.while_level++; 158 } 159 160 void 161 check_getopt_begin_switch(void) 162 { 163 if (ck.while_level > 0) 164 ck.switch_level++; 165 } 166 167 void 168 check_getopt_case_label(int64_t value) 169 { 170 if (ck.switch_level == 1 && value == (char)value) 171 check_unlisted_option((char)value); 172 } 173 174 void 175 check_getopt_end_switch(void) 176 { 177 if (ck.switch_level == 0) 178 return; 179 180 ck.switch_level--; 181 if (ck.switch_level == 0) 182 check_unhandled_option(); 183 } 184 185 void 186 check_getopt_end_while(void) 187 { 188 if (ck.while_level == 0) 189 return; 190 191 ck.while_level--; 192 if (ck.while_level != 0) 193 return; 194 195 free(ck.options); 196 ck.options = NULL; 197 } 198