1 /* $NetBSD: ckgetopt.c,v 1.29 2026/01/10 17:12:26 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 (at) 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.29 2026/01/10 17:12:26 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 ' ' and 65 * ':' should remain. 66 */ 67 pos_t options_pos; 68 int options_lwarn; 69 char *options; 70 char *remaining; 71 72 /* 73 * The nesting level of switch statements, is only modified if 74 * while_level > 0. Only the case labels at switch_level == 1 are 75 * relevant, all nested case labels are ignored. 76 */ 77 int switch_level; 78 } ck; 79 80 /* Return whether tn has the form '(c = getopt(argc, argv, "str")) != -1'. */ 81 static bool 82 is_getopt_condition(const tnode_t *tn, char **out_options) 83 { 84 const function_call *call; 85 const tnode_t *last_arg; 86 const buffer *str; 87 88 if (tn != NULL 89 && tn->tn_op == NE 90 91 && tn->u.ops.right->tn_op == CON 92 && tn->u.ops.right->u.value.v_tspec == INT 93 && tn->u.ops.right->u.value.u.integer == -1 94 95 && tn->u.ops.left->tn_op == ASSIGN 96 && tn->u.ops.left->u.ops.right->tn_op == CALL 97 && (call = tn->u.ops.left->u.ops.right->u.call)->func->tn_op == ADDR 98 && call->func->u.ops.left->tn_op == NAME 99 && strcmp(call->func->u.ops.left->u.sym->s_name, "getopt") == 0 100 && call->args_len == 3 101 && (last_arg = call->args[2]) != NULL 102 && last_arg->tn_op == CVT 103 && last_arg->u.ops.left->tn_op == ADDR 104 && last_arg->u.ops.left->u.ops.left->tn_op == STRING 105 && (str = last_arg->u.ops.left->u.ops.left->u.str_literals)->data != NULL) { 106 buffer buf; 107 buf_init(&buf); 108 quoted_iterator it = { .end = 0 }; 109 while (quoted_next(str, &it)) 110 buf_add_char(&buf, (char)it.value); 111 *out_options = buf.data; 112 return true; 113 } 114 return false; 115 } 116 117 static void 118 check_unlisted_option(char opt) 119 { 120 if (opt == ':' && ck.options[0] != ':') 121 goto warn; 122 123 const char *optptr = strchr(ck.options, opt); 124 if (optptr != NULL) 125 ck.remaining[optptr - ck.options] = ' '; 126 else if (opt != '?') 127 warn: 128 /* option '%c' should be listed in the options string */ 129 warning(339, opt); 130 } 131 132 static void 133 check_unhandled_option(void) 134 { 135 for (const char *opt = ck.remaining; *opt != '\0'; opt++) { 136 if (*opt == ' ' || *opt == ':') 137 continue; 138 139 int prev_lwarn = lwarn; 140 lwarn = ck.options_lwarn; 141 /* option '%c' should be handled in the switch */ 142 warning_at(338, &ck.options_pos, *opt); 143 lwarn = prev_lwarn; 144 } 145 } 146 147 148 void 149 check_getopt_begin_while(const tnode_t *tn) 150 { 151 if (ck.while_level == 0) { 152 if (!is_getopt_condition(tn, &ck.options)) 153 return; 154 ck.options_lwarn = lwarn; 155 ck.options_pos = curr_pos; 156 ck.remaining = xstrdup(ck.options); 157 } 158 ck.while_level++; 159 } 160 161 void 162 check_getopt_begin_switch(void) 163 { 164 if (ck.while_level > 0) 165 ck.switch_level++; 166 } 167 168 void 169 check_getopt_case_label(int64_t value) 170 { 171 if (ck.switch_level == 1 && value == (char)value) 172 check_unlisted_option((char)value); 173 } 174 175 void 176 check_getopt_end_switch(void) 177 { 178 if (ck.switch_level == 0) 179 return; 180 181 ck.switch_level--; 182 if (ck.switch_level == 0) 183 check_unhandled_option(); 184 } 185 186 void 187 check_getopt_end_while(void) 188 { 189 if (ck.while_level == 0) 190 return; 191 192 ck.while_level--; 193 if (ck.while_level != 0) 194 return; 195 196 free(ck.options); 197 ck.options = NULL; 198 free(ck.remaining); 199 ck.remaining = NULL; 200 } 201