1*d7b3b043Srillig /* $NetBSD: ckgetopt.c,v 1.27 2024/03/19 23:19:03 rillig Exp $ */
2746e9c89Srillig
3746e9c89Srillig /*-
4746e9c89Srillig * Copyright (c) 2021 The NetBSD Foundation, Inc.
5746e9c89Srillig * All rights reserved.
6746e9c89Srillig *
7746e9c89Srillig * This code is derived from software contributed to The NetBSD Foundation
8746e9c89Srillig * by Roland Illig <rillig@NetBSD.org>.
9746e9c89Srillig *
10746e9c89Srillig * Redistribution and use in source and binary forms, with or without
11746e9c89Srillig * modification, are permitted provided that the following conditions
12746e9c89Srillig * are met:
13746e9c89Srillig * 1. Redistributions of source code must retain the above copyright
14746e9c89Srillig * notice, this list of conditions and the following disclaimer.
15746e9c89Srillig * 2. Redistributions in binary form must reproduce the above copyright
16746e9c89Srillig * notice, this list of conditions and the following disclaimer in the
17746e9c89Srillig * documentation and/or other materials provided with the distribution.
18746e9c89Srillig *
19746e9c89Srillig * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20746e9c89Srillig * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21746e9c89Srillig * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22746e9c89Srillig * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23746e9c89Srillig * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24746e9c89Srillig * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25746e9c89Srillig * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26746e9c89Srillig * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27746e9c89Srillig * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28746e9c89Srillig * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29746e9c89Srillig * POSSIBILITY OF SUCH DAMAGE.
30746e9c89Srillig */
31746e9c89Srillig
32dbf7816eSrillig #if HAVE_NBTOOL_CONFIG_H
33dbf7816eSrillig #include "nbtool_config.h"
34dbf7816eSrillig #endif
35dbf7816eSrillig
36746e9c89Srillig #include <sys/cdefs.h>
370386644fSrillig #if defined(__RCSID)
38*d7b3b043Srillig __RCSID("$NetBSD: ckgetopt.c,v 1.27 2024/03/19 23:19:03 rillig Exp $");
39746e9c89Srillig #endif
40746e9c89Srillig
41746e9c89Srillig #include <stdbool.h>
42746e9c89Srillig #include <stdlib.h>
43746e9c89Srillig #include <string.h>
44746e9c89Srillig
45746e9c89Srillig #include "lint1.h"
46746e9c89Srillig
47746e9c89Srillig /*
48746e9c89Srillig * In a typical while loop for parsing getopt options, ensure that each
49746e9c89Srillig * option from the options string is handled, and that each handled option
50746e9c89Srillig * is listed in the options string.
51746e9c89Srillig */
52746e9c89Srillig
53cb3c7a45Srillig static struct {
545f975974Srillig /*-
5546a713f7Srillig * 0 means outside a while loop with a getopt call.
5646a713f7Srillig * 1 means directly inside a while loop with a getopt call.
5746a713f7Srillig * > 1 means in a nested while loop; this is used for finishing the
5846a713f7Srillig * check at the correct point.
5946a713f7Srillig */
6046a713f7Srillig int while_level;
6146a713f7Srillig
6246a713f7Srillig /*
6346a713f7Srillig * The options string from the getopt call. Whenever an option is
645f975974Srillig * handled by a case label, it is set to ' '. In the end, only ' ' and
655f975974Srillig * ':' should remain.
6646a713f7Srillig */
67746e9c89Srillig pos_t options_pos;
68746e9c89Srillig char *options;
6946a713f7Srillig
7046a713f7Srillig /*
7146a713f7Srillig * The nesting level of switch statements, is only modified if
7246a713f7Srillig * while_level > 0. Only the case labels at switch_level == 1 are
7346a713f7Srillig * relevant, all nested case labels are ignored.
7446a713f7Srillig */
75746e9c89Srillig int switch_level;
76746e9c89Srillig } ck;
77746e9c89Srillig
7849766a09Srillig /* Return whether tn has the form '(c = getopt(argc, argv, "str")) != -1'. */
79746e9c89Srillig static bool
is_getopt_condition(const tnode_t * tn,char ** out_options)80daf1f532Srillig is_getopt_condition(const tnode_t *tn, char **out_options)
81746e9c89Srillig {
825c34ad8fSrillig const function_call *call;
835c34ad8fSrillig const tnode_t *last_arg;
84c9edda03Srillig const buffer *str;
85db8c1911Srillig
8649766a09Srillig if (tn != NULL
8749766a09Srillig && tn->tn_op == NE
885c34ad8fSrillig
893b4840f1Srillig && tn->u.ops.right->tn_op == CON
903b4840f1Srillig && tn->u.ops.right->u.value.v_tspec == INT
913b4840f1Srillig && tn->u.ops.right->u.value.u.integer == -1
92746e9c89Srillig
933b4840f1Srillig && tn->u.ops.left->tn_op == ASSIGN
943b4840f1Srillig && tn->u.ops.left->u.ops.right->tn_op == CALL
953b4840f1Srillig && (call = tn->u.ops.left->u.ops.right->u.call)->func->tn_op == ADDR
963b4840f1Srillig && call->func->u.ops.left->tn_op == NAME
973b4840f1Srillig && strcmp(call->func->u.ops.left->u.sym->s_name, "getopt") == 0
985c34ad8fSrillig && call->args_len == 3
995c34ad8fSrillig && (last_arg = call->args[2]) != NULL
1005c34ad8fSrillig && last_arg->tn_op == CVT
1013b4840f1Srillig && last_arg->u.ops.left->tn_op == ADDR
1023b4840f1Srillig && last_arg->u.ops.left->u.ops.left->tn_op == STRING
1033b4840f1Srillig && (str = last_arg->u.ops.left->u.ops.left->u.str_literals)->data != NULL) {
104095f4813Srillig buffer buf;
105095f4813Srillig buf_init(&buf);
10659263861Srillig quoted_iterator it = { .end = 0 };
107095f4813Srillig while (quoted_next(str, &it))
108095f4813Srillig buf_add_char(&buf, (char)it.value);
109095f4813Srillig *out_options = buf.data;
110746e9c89Srillig return true;
111746e9c89Srillig }
11249766a09Srillig return false;
11349766a09Srillig }
114746e9c89Srillig
115746e9c89Srillig static void
check_unlisted_option(char opt)116746e9c89Srillig check_unlisted_option(char opt)
117746e9c89Srillig {
118dff6fadbSrillig if (opt == ':' && ck.options[0] != ':')
119dff6fadbSrillig goto warn;
120dff6fadbSrillig
121ffb2a8ebSrillig char *optptr = strchr(ck.options, opt);
122746e9c89Srillig if (optptr != NULL)
1237f300e59Srillig *optptr = ' ';
124ffb2a8ebSrillig else if (opt != '?')
125dff6fadbSrillig warn:
126746e9c89Srillig /* option '%c' should be listed in the options string */
127746e9c89Srillig warning(339, opt);
128746e9c89Srillig }
129746e9c89Srillig
130746e9c89Srillig static void
check_unhandled_option(void)131746e9c89Srillig check_unhandled_option(void)
132746e9c89Srillig {
133ffb2a8ebSrillig for (const char *opt = ck.options; *opt != '\0'; opt++) {
134746e9c89Srillig if (*opt == ' ' || *opt == ':')
135746e9c89Srillig continue;
136746e9c89Srillig
137746e9c89Srillig /* option '%c' should be handled in the switch */
1386de49ab0Srillig warning_at(338, &ck.options_pos, *opt);
139746e9c89Srillig }
140746e9c89Srillig }
141746e9c89Srillig
142746e9c89Srillig
143746e9c89Srillig void
check_getopt_begin_while(const tnode_t * tn)144746e9c89Srillig check_getopt_begin_while(const tnode_t *tn)
145746e9c89Srillig {
14646a713f7Srillig if (ck.while_level == 0) {
147daf1f532Srillig if (!is_getopt_condition(tn, &ck.options))
14846a713f7Srillig return;
149746e9c89Srillig ck.options_pos = curr_pos;
150746e9c89Srillig }
151746e9c89Srillig ck.while_level++;
152746e9c89Srillig }
153746e9c89Srillig
154746e9c89Srillig void
check_getopt_begin_switch(void)155746e9c89Srillig check_getopt_begin_switch(void)
156746e9c89Srillig {
157746e9c89Srillig if (ck.while_level > 0)
158746e9c89Srillig ck.switch_level++;
159746e9c89Srillig }
160746e9c89Srillig
161746e9c89Srillig void
check_getopt_case_label(int64_t value)162746e9c89Srillig check_getopt_case_label(int64_t value)
163746e9c89Srillig {
164746e9c89Srillig if (ck.switch_level == 1 && value == (char)value)
165746e9c89Srillig check_unlisted_option((char)value);
166746e9c89Srillig }
167746e9c89Srillig
168746e9c89Srillig void
check_getopt_end_switch(void)169746e9c89Srillig check_getopt_end_switch(void)
170746e9c89Srillig {
171746e9c89Srillig if (ck.switch_level == 0)
172746e9c89Srillig return;
173746e9c89Srillig
174746e9c89Srillig ck.switch_level--;
175746e9c89Srillig if (ck.switch_level == 0)
176746e9c89Srillig check_unhandled_option();
177746e9c89Srillig }
178746e9c89Srillig
179746e9c89Srillig void
check_getopt_end_while(void)180746e9c89Srillig check_getopt_end_while(void)
181746e9c89Srillig {
182746e9c89Srillig if (ck.while_level == 0)
183746e9c89Srillig return;
184746e9c89Srillig
185746e9c89Srillig ck.while_level--;
186746e9c89Srillig if (ck.while_level != 0)
187746e9c89Srillig return;
188746e9c89Srillig
189746e9c89Srillig free(ck.options);
190746e9c89Srillig ck.options = NULL;
191746e9c89Srillig }
192