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