xref: /netbsd-src/usr.bin/xlint/lint1/ckctype.c (revision 6bf2647feca48e066f181ac19000bffe75e2a408)
1 /* $NetBSD: ckctype.c,v 1.13 2024/12/18 18:14:54 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@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 
38 #if defined(__RCSID)
39 __RCSID("$NetBSD: ckctype.c,v 1.13 2024/12/18 18:14:54 rillig Exp $");
40 #endif
41 
42 #include <string.h>
43 
44 #include "lint1.h"
45 
46 /*
47  * Check that the functions from <ctype.h> are used properly.  They must not
48  * be called with an argument of type 'char'.  In such a case, the argument
49  * must be converted to 'unsigned char'.  The tricky thing is that even though
50  * the parameter type is declared as 'int', a 'char' argument must not be
51  * directly cast to 'int', as that would preserve negative argument values.
52  *
53  * See also:
54  *	ctype(3)
55  *	https://stackoverflow.com/a/60696378
56  */
57 
58 static bool
59 is_ctype_function(const char *name)
60 {
61 
62 	if (name[0] == 'i' && name[1] == 's')
63 		return strcmp(name, "isalnum") == 0 ||
64 		    strcmp(name, "isalpha") == 0 ||
65 		    strcmp(name, "isblank") == 0 ||
66 		    strcmp(name, "iscntrl") == 0 ||
67 		    strcmp(name, "isdigit") == 0 ||
68 		    strcmp(name, "isgraph") == 0 ||
69 		    strcmp(name, "islower") == 0 ||
70 		    strcmp(name, "isprint") == 0 ||
71 		    strcmp(name, "ispunct") == 0 ||
72 		    strcmp(name, "isspace") == 0 ||
73 		    strcmp(name, "isupper") == 0 ||
74 		    strcmp(name, "isxdigit") == 0;
75 
76 	if (name[0] == 't' && name[1] == 'o')
77 		return strcmp(name, "tolower") == 0 ||
78 		    strcmp(name, "toupper") == 0;
79 
80 	return false;
81 }
82 
83 static bool
84 is_ctype_table(const char *name)
85 {
86 
87 	/* NetBSD sys/ctype_bits.h 1.6 from 2016-01-22 */
88 	if (strcmp(name, "_ctype_tab_") == 0 ||
89 	    strcmp(name, "_tolower_tab_") == 0 ||
90 	    strcmp(name, "_toupper_tab_") == 0)
91 		return true;
92 
93 	/* NetBSD sys/ctype_bits.h 1.1 from 2010-06-01 */
94 	return strcmp(name, "_ctype_") == 0;
95 }
96 
97 static void
98 check_ctype_arg(const char *func, const tnode_t *arg)
99 {
100 	const tnode_t *on, *cn;
101 
102 	for (on = arg; on->tn_op == CVT; on = on->u.ops.left)
103 		if (on->tn_type->t_tspec == UCHAR)
104 			return;
105 	if (on->tn_type->t_tspec == UCHAR)
106 		return;
107 
108 	if (arg->tn_op == CVT && arg->tn_cast) {
109 		/* argument to '%s' must be cast to 'unsigned char', not ... */
110 		warning(342, func, type_name(arg->tn_type));
111 		return;
112 	}
113 
114 	cn = before_conversion(arg);
115 	if (cn->tn_type->t_tspec != UCHAR && cn->tn_type->t_tspec != INT) {
116 		/* argument to '%s' must be 'unsigned char' or EOF, not '%s' */
117 		warning(341, func, type_name(cn->tn_type));
118 		return;
119 	}
120 }
121 
122 void
123 check_ctype_function_call(const function_call *call)
124 {
125 
126 	if (call->args_len == 1 &&
127 	    call->func->tn_op == NAME &&
128 	    is_ctype_function(call->func->u.sym->s_name))
129 		check_ctype_arg(call->func->u.sym->s_name, call->args[0]);
130 }
131 
132 void
133 check_ctype_macro_invocation(const tnode_t *ln, const tnode_t *rn)
134 {
135 
136 	if (ln->tn_op == PLUS &&
137 	    ln->u.ops.left != NULL &&
138 	    ln->u.ops.left->tn_op == LOAD &&
139 	    ln->u.ops.left->u.ops.left != NULL &&
140 	    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 		check_ctype_arg("function from <ctype.h>", rn);
143 }
144