Home | History | Annotate | Line # | Download | only in lint1
ckgetopt.c revision 1.4
      1 /* $NetBSD: ckgetopt.c,v 1.4 2021/02/20 09:57:02 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) && !defined(lint)
     38 __RCSID("$NetBSD: ckgetopt.c,v 1.4 2021/02/20 09:57:02 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 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 unhandled_options.
     65 	 * In the end, only ' ' and ':' should remain in unhandled_options.
     66 	 */
     67 	pos_t options_pos;
     68 	char *options;
     69 	char *unhandled_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 
     80 static bool
     81 is_getopt_call(const tnode_t *tn, char **out_options)
     82 {
     83 	if (tn == NULL)
     84 		return false;
     85 	if (tn->tn_op != NE)
     86 		return false;
     87 	if (tn->tn_left->tn_op != ASSIGN)
     88 		return false;
     89 
     90 	const tnode_t *call = tn->tn_left->tn_right;
     91 	if (call->tn_op != CALL)
     92 		return false;
     93 	if (call->tn_left->tn_op != ADDR)
     94 		return false;
     95 	if (call->tn_left->tn_left->tn_op != NAME)
     96 		return false;
     97 	if (strcmp(call->tn_left->tn_left->tn_sym->s_name, "getopt") != 0)
     98 		return false;
     99 
    100 	if (call->tn_right->tn_op != PUSH)
    101 		return false;
    102 
    103 	const tnode_t *last_arg = call->tn_right->tn_left;
    104 	if (last_arg->tn_op != CVT)
    105 		return false;
    106 	if (last_arg->tn_left->tn_op != ADDR)
    107 		return false;
    108 	if (last_arg->tn_left->tn_left->tn_op != STRING)
    109 		return false;
    110 	if (last_arg->tn_left->tn_left->tn_string->st_tspec != CHAR)
    111 		return false;
    112 
    113 	*out_options = xstrdup(
    114 	    (const char *)last_arg->tn_left->tn_left->tn_string->st_cp);
    115 	return true;
    116 }
    117 
    118 static void
    119 check_unlisted_option(char opt)
    120 {
    121 	lint_assert(ck.options != NULL);
    122 
    123 	if (opt == '?')
    124 		return;
    125 
    126 	const char *optptr = strchr(ck.options, opt);
    127 	if (optptr != NULL)
    128 		ck.unhandled_options[optptr - ck.options] = ' ';
    129 	else {
    130 		/* option '%c' should be listed in the options string */
    131 		warning(339, opt);
    132 		return;
    133 	}
    134 }
    135 
    136 static void
    137 check_unhandled_option(void)
    138 {
    139 	lint_assert(ck.unhandled_options != NULL);
    140 
    141 	for (const char *opt = ck.unhandled_options; *opt != '\0'; opt++) {
    142 		if (*opt == ' ' || *opt == ':')
    143 			continue;
    144 
    145 		pos_t prev_pos = curr_pos;
    146 		curr_pos = ck.options_pos;
    147 		/* option '%c' should be handled in the switch */
    148 		warning(338, *opt);
    149 		curr_pos = prev_pos;
    150 	}
    151 }
    152 
    153 
    154 void
    155 check_getopt_begin_while(const tnode_t *tn)
    156 {
    157 	if (ck.while_level == 0) {
    158 		if (!is_getopt_call(tn, &ck.options))
    159 			return;
    160 		ck.unhandled_options = xstrdup(ck.options);
    161 		ck.options_pos = curr_pos;
    162 	}
    163 	ck.while_level++;
    164 }
    165 
    166 void
    167 check_getopt_begin_switch(void)
    168 {
    169 	if (ck.while_level > 0)
    170 		ck.switch_level++;
    171 }
    172 
    173 
    174 void
    175 check_getopt_case_label(int64_t value)
    176 {
    177 	if (ck.switch_level == 1 && value == (char)value)
    178 		check_unlisted_option((char)value);
    179 }
    180 
    181 void
    182 check_getopt_end_switch(void)
    183 {
    184 	if (ck.switch_level == 0)
    185 		return;
    186 
    187 	ck.switch_level--;
    188 	if (ck.switch_level == 0)
    189 		check_unhandled_option();
    190 }
    191 
    192 void
    193 check_getopt_end_while(void)
    194 {
    195 	if (ck.while_level == 0)
    196 		return;
    197 
    198 	ck.while_level--;
    199 	if (ck.while_level != 0)
    200 		return;
    201 
    202 	free(ck.options);
    203 	free(ck.unhandled_options);
    204 	ck.options = NULL;
    205 	ck.unhandled_options = NULL;
    206 }
    207