xref: /netbsd-src/usr.bin/xlint/lint1/ckgetopt.c (revision d7b3b0431942747a82214a2bcb5f237506d570fe)
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