#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif
#include <sys/cdefs.h>
#if defined(__RCSID)
__RCSID("$NetBSD: ckgetopt.c,v 1.29 2026/01/10 17:12:26 rillig Exp $");
#endif
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "lint1.h"
static struct {
int while_level;
pos_t options_pos;
int options_lwarn;
char *options;
char *remaining;
int switch_level;
} ck;
static bool
is_getopt_condition(const tnode_t *tn, char **out_options)
{
const function_call *call;
const tnode_t *last_arg;
const buffer *str;
if (tn != NULL
&& tn->tn_op == NE
&& tn->u.ops.right->tn_op == CON
&& tn->u.ops.right->u.value.v_tspec == INT
&& tn->u.ops.right->u.value.u.integer == -1
&& tn->u.ops.left->tn_op == ASSIGN
&& tn->u.ops.left->u.ops.right->tn_op == CALL
&& (call = tn->u.ops.left->u.ops.right->u.call)->func->tn_op == ADDR
&& call->func->u.ops.left->tn_op == NAME
&& strcmp(call->func->u.ops.left->u.sym->s_name, "getopt") == 0
&& call->args_len == 3
&& (last_arg = call->args[2]) != NULL
&& last_arg->tn_op == CVT
&& last_arg->u.ops.left->tn_op == ADDR
&& last_arg->u.ops.left->u.ops.left->tn_op == STRING
&& (str = last_arg->u.ops.left->u.ops.left->u.str_literals)->data != NULL) {
buffer buf;
buf_init(&buf);
quoted_iterator it = { .end = 0 };
while (quoted_next(str, &it))
buf_add_char(&buf, (char)it.value);
*out_options = buf.data;
return true;
}
return false;
}
static void
check_unlisted_option(char opt)
{
if (opt == ':' && ck.options[0] != ':')
goto warn;
const char *optptr = strchr(ck.options, opt);
if (optptr != NULL)
ck.remaining[optptr - ck.options] = ' ';
else if (opt != '?')
warn:
warning(339, opt);
}
static void
check_unhandled_option(void)
{
for (const char *opt = ck.remaining; *opt != '\0'; opt++) {
if (*opt == ' ' || *opt == ':')
continue;
int prev_lwarn = lwarn;
lwarn = ck.options_lwarn;
warning_at(338, &ck.options_pos, *opt);
lwarn = prev_lwarn;
}
}
void
check_getopt_begin_while(const tnode_t *tn)
{
if (ck.while_level == 0) {
if (!is_getopt_condition(tn, &ck.options))
return;
ck.options_lwarn = lwarn;
ck.options_pos = curr_pos;
ck.remaining = xstrdup(ck.options);
}
ck.while_level++;
}
void
check_getopt_begin_switch(void)
{
if (ck.while_level > 0)
ck.switch_level++;
}
void
check_getopt_case_label(int64_t value)
{
if (ck.switch_level == 1 && value == (char)value)
check_unlisted_option((char)value);
}
void
check_getopt_end_switch(void)
{
if (ck.switch_level == 0)
return;
ck.switch_level--;
if (ck.switch_level == 0)
check_unhandled_option();
}
void
check_getopt_end_while(void)
{
if (ck.while_level == 0)
return;
ck.while_level--;
if (ck.while_level != 0)
return;
free(ck.options);
ck.options = NULL;
free(ck.remaining);
ck.remaining = NULL;
}