Skip to content

Commit 321d5da

Browse files
committed
lint: in _Generic expressions, only evaluate the matching branches
1 parent bbdda05 commit 321d5da

6 files changed

Lines changed: 113 additions & 88 deletions

File tree

tests/usr.bin/xlint/lint1/c11_generic_expression.c

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: c11_generic_expression.c,v 1.25 2026/03/16 20:09:15 rillig Exp $ */
1+
/* $NetBSD: c11_generic_expression.c,v 1.26 2026/03/18 06:17:55 rillig Exp $ */
22
# 3 "c11_generic_expression.c"
33

44
/* lint1-extra-flags: -X 351 */
@@ -100,7 +100,7 @@ primary_expression(void)
100100
}
101101

102102
/*
103-
* The types don't match, therefore build_generic_selection returns NULL,
103+
* The types don't match, therefore the _Generic selection evaluates to NULL,
104104
* which is then silently ignored by init_expr. This situation is already
105105
* covered by the compilers, so there is no need for lint to double-check it.
106106
*/
@@ -143,17 +143,53 @@ skip_blank(const char *p)
143143
{
144144
static const unsigned short *_ctype_tab_;
145145

146-
while (
147-
// TODO: In a _Generic expression, only evaluate the
148-
// result expression, but not the others. Only parse
149-
// the others, don't determine their type.
150-
_Generic(
151-
*p + 1 / 0,
152-
/* expect+1: warning: argument to 'function from <ctype.h>' must be 'unsigned char' or EOF, not 'char' [341] */
153-
unsigned char: (_ctype_tab_ + 1)[*p] & 0x0200,
154-
/* expect+1: warning: argument to 'function from <ctype.h>' must be 'unsigned char' or EOF, not 'char' [341] */
155-
int: (_ctype_tab_ + 1)[*p] & 0x0200
156-
)
157-
)
146+
while (_Generic(
147+
p[1 / 0],
148+
unsigned char: (_ctype_tab_ + 1)[*p] & 0x0200,
149+
int: (_ctype_tab_ + 1)[*p] & 0x0200
150+
))
158151
p++;
159152
}
153+
154+
const char *
155+
evaluation_mode(void)
156+
{
157+
return _Generic(
158+
0,
159+
160+
// Only parse since 'double' does not match 'int'.
161+
double: 1 / 0,
162+
163+
int: "matched",
164+
165+
// Only parse since 'const char *' does not match 'int'.
166+
const char *: 1 / 0,
167+
168+
// Only parse since a previous branch already matched,
169+
// so the fallback branch is not used.
170+
default: 1 / 0
171+
);
172+
}
173+
174+
const char *
175+
evaluation_mode_early_default(void)
176+
{
177+
return _Generic(
178+
0,
179+
180+
// Lint decides at parse time whether it fully evaluates the
181+
// branch or only parses it. Since the default branch comes
182+
// first, it is not yet known whether any of the other branches
183+
// will match, so evaluate it.
184+
/* expect+1: error: division by 0 [139] */
185+
default: 1 / 0,
186+
187+
// Only parse since 'double' does not match 'int'.
188+
double: 1 / 0,
189+
190+
int: "matched",
191+
192+
// Only parse since 'const char *' does not match 'int'.
193+
const char *: 1 / 0
194+
);
195+
}

usr.bin/xlint/lint1/cgram.y

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
%{
2-
/* $NetBSD: cgram.y,v 1.537 2026/03/16 20:09:15 rillig Exp $ */
2+
/* $NetBSD: cgram.y,v 1.538 2026/03/18 06:17:55 rillig Exp $ */
33

44
/*
55
* Copyright (c) 1996 Christopher G. Demetriou. All Rights Reserved.
@@ -35,7 +35,7 @@
3535

3636
#include <sys/cdefs.h>
3737
#if defined(__RCSID)
38-
__RCSID("$NetBSD: cgram.y,v 1.537 2026/03/16 20:09:15 rillig Exp $");
38+
__RCSID("$NetBSD: cgram.y,v 1.538 2026/03/18 06:17:55 rillig Exp $");
3939
#endif
4040

4141
#include <limits.h>
@@ -148,6 +148,15 @@ new_attribute(const sbuf_t *prefix, const sbuf_t *name,
148148
return attr;
149149
}
150150

151+
static tnode_t *
152+
unconst_tnode(const tnode_t *p)
153+
{
154+
void *r;
155+
156+
memcpy(&r, &p, sizeof(r));
157+
return r;
158+
}
159+
151160
#if YYDEBUG && YYBYACC
152161
#define YYSTYPE_TOSTRING cgram_to_string
153162
#endif
@@ -175,7 +184,7 @@ new_attribute(const sbuf_t *prefix, const sbuf_t *name,
175184
buffer *y_string;
176185
qual_ptr *y_qual_ptr;
177186
bool y_seen_statement;
178-
struct generic_association *y_generic;
187+
struct generic_selection y_generic_selection;
179188
array_size y_array_size;
180189
bool y_in_system_header;
181190
designation y_designation;
@@ -238,7 +247,7 @@ new_attribute(const sbuf_t *prefix, const sbuf_t *name,
238247
fprintf(yyo, "%s *", type_qualifiers_string($$->qualifiers));
239248
} <y_qual_ptr>
240249
%printer { fprintf(yyo, "%s", $$ ? "yes" : "no"); } <y_seen_statement>
241-
%printer { fprintf(yyo, "%s", type_name($$->ga_arg)); } <y_generic>
250+
%printer { fprintf(yyo, "%s", type_name($$.expr_type)); } <y_generic_selection>
242251
%printer { fprintf(yyo, "%d", $$.dim); } <y_array_size>
243252
%printer { fprintf(yyo, "%s", $$ ? "yes" : "no"); } <y_in_system_header>
244253
%printer {
@@ -354,8 +363,7 @@ new_attribute(const sbuf_t *prefix, const sbuf_t *name,
354363
%type <y_tnode> primary_expression
355364
%type <y_designation> member_designator
356365
%type <y_tnode> generic_selection
357-
%type <y_generic> generic_assoc_list
358-
%type <y_generic> generic_association
366+
%type <y_generic_selection> generic_inner
359367
%type <y_tnode> postfix_expression
360368
%type <y_tnode> gcc_statement_expr_list
361369
%type <y_tnode> gcc_statement_expr_item
@@ -605,37 +613,46 @@ member_designator:
605613

606614
/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C23 6.5.2.1 */
607615
generic_selection:
608-
T_GENERIC T_LPAREN {
609-
push_evaluation_mode(EM_TYPE);
610-
} assignment_expression {
611-
pop_evaluation_mode();
612-
} T_COMMA generic_assoc_list T_RPAREN {
616+
T_GENERIC {
613617
/* generic selection requires C11 or later */
614618
c11ism(345);
615-
$$ = build_generic_selection($4, $7);
619+
} T_LPAREN generic_inner T_RPAREN {
620+
$$ = $4.matched != NULL ? $4.matched : $4.fallback;
616621
}
617622
;
618623

619-
/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C23 6.5.2.1 */
620-
generic_assoc_list:
621-
generic_association
622-
| generic_assoc_list T_COMMA generic_association {
623-
$3->ga_prev = $1;
624-
$$ = $3;
625-
}
626-
;
624+
/* The rule 'generic_assoc_list' is inlined into 'generic_inner'. */
625+
/* The rule 'generic_association' is inlined into 'generic_inner'. */
627626

628-
/* K&R ---, C90 ---, C99 ---, C11 6.5.1.1, C23 6.5.2.1 */
629-
generic_association:
630-
type_name T_COLON assignment_expression {
631-
$$ = block_zero_alloc(sizeof(*$$), "generic");
632-
$$->ga_arg = $1;
633-
$$->ga_result = $3;
634-
}
635-
| T_DEFAULT T_COLON assignment_expression {
636-
$$ = block_zero_alloc(sizeof(*$$), "generic");
637-
$$->ga_arg = NULL;
638-
$$->ga_result = $3;
627+
generic_inner:
628+
{
629+
push_evaluation_mode(EM_TYPE);
630+
} assignment_expression {
631+
pop_evaluation_mode();
632+
} {
633+
$$ = (struct generic_selection){
634+
/* C23 6.5.2.1p2 */
635+
.expr_type = $2 != NULL
636+
? cconv(unconst_tnode($2))->tn_type
637+
: NULL
638+
};
639+
}
640+
| generic_inner T_COMMA type_name T_COLON {
641+
$1.assoc_matched = $1.expr_type != NULL && $3 != NULL
642+
&& types_compatible($1.expr_type, $3, true, false, NULL);
643+
push_evaluation_mode($1.assoc_matched ? EM_EVAL : EM_PARSE);
644+
} assignment_expression {
645+
pop_evaluation_mode();
646+
$$ = $1;
647+
if ($$.assoc_matched)
648+
$$.matched = $6;
649+
}
650+
| generic_inner T_COMMA T_DEFAULT T_COLON {
651+
push_evaluation_mode($1.matched != NULL ? EM_PARSE : EM_EVAL);
652+
} assignment_expression {
653+
pop_evaluation_mode();
654+
$$ = $1;
655+
$$.fallback = $6;
639656
}
640657
;
641658

usr.bin/xlint/lint1/ckctype.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: ckctype.c,v 1.13 2024/12/18 18:14:54 rillig Exp $ */
1+
/* $NetBSD: ckctype.c,v 1.14 2026/03/18 06:17:55 rillig Exp $ */
22

33
/*-
44
* Copyright (c) 2021 The NetBSD Foundation, Inc.
@@ -36,7 +36,7 @@
3636
#include <sys/cdefs.h>
3737

3838
#if defined(__RCSID)
39-
__RCSID("$NetBSD: ckctype.c,v 1.13 2024/12/18 18:14:54 rillig Exp $");
39+
__RCSID("$NetBSD: ckctype.c,v 1.14 2026/03/18 06:17:55 rillig Exp $");
4040
#endif
4141

4242
#include <string.h>
@@ -125,7 +125,8 @@ check_ctype_function_call(const function_call *call)
125125

126126
if (call->args_len == 1 &&
127127
call->func->tn_op == NAME &&
128-
is_ctype_function(call->func->u.sym->s_name))
128+
is_ctype_function(call->func->u.sym->s_name) &&
129+
is_evaluation_mode(EM_TYPE))
129130
check_ctype_arg(call->func->u.sym->s_name, call->args[0]);
130131
}
131132

@@ -138,6 +139,7 @@ check_ctype_macro_invocation(const tnode_t *ln, const tnode_t *rn)
138139
ln->u.ops.left->tn_op == LOAD &&
139140
ln->u.ops.left->u.ops.left != NULL &&
140141
ln->u.ops.left->u.ops.left->tn_op == NAME &&
141-
is_ctype_table(ln->u.ops.left->u.ops.left->u.sym->s_name))
142+
is_ctype_table(ln->u.ops.left->u.ops.left->u.sym->s_name) &&
143+
is_evaluation_mode(EM_TYPE))
142144
check_ctype_arg("function from <ctype.h>", rn);
143145
}

usr.bin/xlint/lint1/externs1.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: externs1.h,v 1.244 2026/03/15 07:55:59 rillig Exp $ */
1+
/* $NetBSD: externs1.h,v 1.245 2026/03/18 06:17:55 rillig Exp $ */
22

33
/*
44
* Copyright (c) 1994, 1995 Jochen Pohl
@@ -286,8 +286,6 @@ bool is_compiler_builtin(const char *);
286286
tnode_t *build_constant(type_t *, val_t *);
287287
tnode_t *build_name(sym_t *, bool);
288288
tnode_t *build_string(buffer *);
289-
tnode_t *build_generic_selection(const tnode_t *,
290-
struct generic_association *);
291289

292290
tnode_t *build_binary(tnode_t *, op_t, bool, tnode_t *);
293291
tnode_t *build_unary(op_t, bool, tnode_t *);

usr.bin/xlint/lint1/lint1.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: lint1.h,v 1.239 2026/03/15 07:55:59 rillig Exp $ */
1+
/* $NetBSD: lint1.h,v 1.240 2026/03/18 06:17:55 rillig Exp $ */
22

33
/*
44
* Copyright (c) 1996 Christopher G. Demetriou. All Rights Reserved.
@@ -329,10 +329,11 @@ struct tnode {
329329
} u;
330330
};
331331

332-
struct generic_association {
333-
type_t *ga_arg; /* NULL means default or error */
334-
tnode_t *ga_result; /* NULL means error */
335-
struct generic_association *ga_prev;
332+
struct generic_selection {
333+
const type_t *expr_type;
334+
tnode_t *fallback;
335+
tnode_t *matched;
336+
bool assoc_matched;
336337
};
337338

338339
typedef struct {

usr.bin/xlint/lint1/tree.c

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* $NetBSD: tree.c,v 1.703 2026/03/15 07:56:00 rillig Exp $ */
1+
/* $NetBSD: tree.c,v 1.704 2026/03/18 06:17:55 rillig Exp $ */
22

33
/*
44
* Copyright (c) 1994, 1995 Jochen Pohl
@@ -37,7 +37,7 @@
3737

3838
#include <sys/cdefs.h>
3939
#if defined(__RCSID)
40-
__RCSID("$NetBSD: tree.c,v 1.703 2026/03/15 07:56:00 rillig Exp $");
40+
__RCSID("$NetBSD: tree.c,v 1.704 2026/03/18 06:17:55 rillig Exp $");
4141
#endif
4242

4343
#include <float.h>
@@ -949,35 +949,6 @@ build_string(buffer *lit)
949949
return n;
950950
}
951951

952-
static tnode_t *
953-
unconst_tnode(const tnode_t *p)
954-
{
955-
void *r;
956-
957-
memcpy(&r, &p, sizeof(r));
958-
return r;
959-
}
960-
961-
tnode_t *
962-
build_generic_selection(const tnode_t *expr,
963-
struct generic_association *sel)
964-
{
965-
tnode_t *default_result = NULL;
966-
967-
if (expr != NULL)
968-
expr = cconv(unconst_tnode(expr)); /* C23 6.5.2.1p2 */
969-
970-
for (; sel != NULL; sel = sel->ga_prev) {
971-
if (expr != NULL &&
972-
types_compatible(sel->ga_arg, expr->tn_type,
973-
true, false, NULL))
974-
return sel->ga_result;
975-
if (sel->ga_arg == NULL)
976-
default_result = sel->ga_result;
977-
}
978-
return default_result;
979-
}
980-
981952
static bool
982953
is_out_of_char_range(const tnode_t *tn)
983954
{

0 commit comments

Comments
 (0)