1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2021 SUSE LLC <rpalethorpe@suse.com>
4 *
5 * Sparse allows us to perform checks on the AST (struct symbol) or on
6 * a linearized representation. In the latter case we are given a set
7 * of entry points (functions) containing basic blocks of
8 * instructions.
9 *
10 * The basic blocks contain byte code in SSA form. This is similar to
11 * the intermediate representation most compilers use during
12 * optimisation.
13 */
14 #include <stdarg.h>
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include <unistd.h>
20 #include <fcntl.h>
21
22 #include "lib.h"
23 #include "allocate.h"
24 #include "opcode.h"
25 #include "token.h"
26 #include "parse.h"
27 #include "symbol.h"
28 #include "expression.h"
29 #include "linearize.h"
30
31 /* The rules for test, library and tool code are different */
32 enum ltp_tu_kind {
33 LTP_LIB,
34 LTP_OTHER,
35 };
36
37 static enum ltp_tu_kind tu_kind = LTP_OTHER;
38
39 /* Check for LTP-002
40 *
41 * Inspects the destination symbol of each store instruction. If it is
42 * TST_RET or TST_ERR then emit a warning.
43 */
check_lib_sets_TEST_vars(const struct instruction * insn)44 static void check_lib_sets_TEST_vars(const struct instruction *insn)
45 {
46 static struct ident *TST_RES_id, *TST_ERR_id;
47
48 if (!TST_RES_id) {
49 TST_RES_id = built_in_ident("TST_RET");
50 TST_ERR_id = built_in_ident("TST_ERR");
51 }
52
53 if (insn->opcode != OP_STORE)
54 return;
55 if (insn->src->ident != TST_RES_id &&
56 insn->src->ident != TST_ERR_id)
57 return;
58
59 warning(insn->pos,
60 "LTP-002: Library should not write to TST_RET or TST_ERR");
61 }
62
do_basicblock_checks(struct basic_block * bb)63 static void do_basicblock_checks(struct basic_block *bb)
64 {
65 struct instruction *insn;
66
67 FOR_EACH_PTR(bb->insns, insn) {
68 if (!bb_reachable(insn->bb))
69 continue;
70
71 if (tu_kind == LTP_LIB)
72 check_lib_sets_TEST_vars(insn);
73 } END_FOR_EACH_PTR(insn);
74 }
75
do_entrypoint_checks(struct entrypoint * ep)76 static void do_entrypoint_checks(struct entrypoint *ep)
77 {
78 struct basic_block *bb;
79
80 FOR_EACH_PTR(ep->bbs, bb) {
81 do_basicblock_checks(bb);
82 } END_FOR_EACH_PTR(bb);
83 }
84
85 /* The old API can not comply with the rules. So when we see one of
86 * these symbols we know that it will result in further
87 * warnings. Probably these will suggest inappropriate things. Usually
88 * these symbols should be removed and the new API used
89 * instead. Otherwise they can be ignored until all tests have been
90 * converted to the new API.
91 */
check_symbol_deprecated(const struct symbol * const sym)92 static bool check_symbol_deprecated(const struct symbol *const sym)
93 {
94 static struct ident *TCID_id, *TST_TOTAL_id;
95 const struct ident *id = sym->ident;
96
97 if (!TCID_id) {
98 TCID_id = built_in_ident("TCID");
99 TST_TOTAL_id = built_in_ident("TST_TOTAL");
100 }
101
102 if (id != TCID_id && id != TST_TOTAL_id)
103 return false;
104
105 warning(sym->pos,
106 "Ignoring deprecated API symbol: '%s'. Should this code be converted to the new API?",
107 show_ident(id));
108
109 return true;
110 }
111
112 /* Check for LTP-003 and LTP-004
113 *
114 * Try to find cases where the static keyword was forgotten.
115 */
check_symbol_visibility(const struct symbol * const sym)116 static void check_symbol_visibility(const struct symbol *const sym)
117 {
118 const unsigned long mod = sym->ctype.modifiers;
119 const char *const name = show_ident(sym->ident);
120 const int has_lib_prefix = !strncmp("tst_", name, 4) ||
121 !strncmp("TST_", name, 4) ||
122 !strncmp("ltp_", name, 4) ||
123 !strncmp("safe_", name, 5);
124
125 if (!(mod & MOD_TOPLEVEL))
126 return;
127
128 if (has_lib_prefix && (mod & MOD_STATIC) && !(mod & MOD_INLINE)) {
129 warning(sym->pos,
130 "LTP-003: Symbol '%s' has the LTP public library prefix, but is static (private).",
131 name);
132 return;
133 }
134
135 if ((mod & MOD_STATIC))
136 return;
137
138 if (tu_kind == LTP_LIB && !has_lib_prefix) {
139 warning(sym->pos,
140 "LTP-003: Symbol '%s' is a public library function, but is missing the 'tst_' prefix",
141 name);
142 return;
143 }
144
145 if (sym->same_symbol)
146 return;
147
148 if (sym->ident == &main_ident)
149 return;
150
151 warning(sym->pos,
152 "Symbol '%s' has no prototype or library ('tst_') prefix. Should it be static?",
153 name);
154 }
155
156 /* See base_type() in dissect.c */
unwrap_base_type(const struct symbol * sym)157 static struct symbol *unwrap_base_type(const struct symbol *sym)
158 {
159 switch (sym->ctype.base_type->type) {
160 case SYM_ARRAY:
161 case SYM_NODE:
162 case SYM_PTR:
163 return unwrap_base_type(sym->ctype.base_type);
164 default:
165 return sym->ctype.base_type;
166 }
167 }
168
169 /* Checks if some struct array initializer is terminated with a blank
170 * (zeroed) item i.e. {}
171 */
is_terminated_with_null_struct(const struct symbol * const sym)172 static bool is_terminated_with_null_struct(const struct symbol *const sym)
173 {
174 const struct expression *const arr_init = sym->initializer;
175 const struct expression *item_init =
176 last_ptr_list((struct ptr_list *)arr_init->expr_list);
177
178 if (item_init->type == EXPR_POS)
179 item_init = item_init->init_expr;
180
181 return ptr_list_empty((struct ptr_list *)item_init->expr_list);
182 }
183
184 /* Check for (one instance of) LTP-005
185 *
186 * The tags array is only accessed when the test fails. So we perform
187 * a static check to ensure it ends with {}
188 */
check_tag_initializer(const struct symbol * const sym)189 static void check_tag_initializer(const struct symbol *const sym)
190 {
191 if (is_terminated_with_null_struct(sym))
192 return;
193
194 warning(sym->pos,
195 "LTP-005: test.tags array doesn't appear to be null-terminated; did you forget to add '{}' as the final entry?");
196 }
197
198 /* Find struct tst_test test = { ... } and perform tests on its initializer */
check_test_struct(const struct symbol * const sym)199 static void check_test_struct(const struct symbol *const sym)
200 {
201 static struct ident *tst_test, *tst_test_test, *tst_tag;
202 struct ident *ctype_name = NULL;
203 struct expression *init = sym->initializer;
204 struct expression *entry;
205
206 if (!sym->ctype.base_type)
207 return;
208
209 ctype_name = sym->ctype.base_type->ident;
210
211 if (!init)
212 return;
213
214 if (!tst_test_test) {
215 tst_test = built_in_ident("tst_test");
216 tst_test_test = built_in_ident("test");
217 tst_tag = built_in_ident("tst_tag");
218 }
219
220 if (sym->ident != tst_test_test)
221 return;
222
223 if (ctype_name != tst_test)
224 return;
225
226 FOR_EACH_PTR(init->expr_list, entry) {
227 if (entry->init_expr->type != EXPR_SYMBOL)
228 continue;
229
230 const struct symbol *entry_init = entry->init_expr->symbol;
231 const struct symbol *entry_ctype = unwrap_base_type(entry_init);
232
233 if (entry_ctype->ident == tst_tag)
234 check_tag_initializer(entry_init);
235 } END_FOR_EACH_PTR(entry);
236
237 }
238
239 /* AST level checks */
do_symbol_checks(struct symbol * sym)240 static void do_symbol_checks(struct symbol *sym)
241 {
242 if (check_symbol_deprecated(sym))
243 return;
244
245 check_symbol_visibility(sym);
246 check_test_struct(sym);
247 }
248
249 /* Compile the AST into a graph of basicblocks */
process_symbols(struct symbol_list * list)250 static void process_symbols(struct symbol_list *list)
251 {
252 struct symbol *sym;
253
254 FOR_EACH_PTR(list, sym) {
255 struct entrypoint *ep;
256
257 do_symbol_checks(sym);
258
259 expand_symbol(sym);
260 ep = linearize_symbol(sym);
261 if (!ep || !ep->entry)
262 continue;
263
264 do_entrypoint_checks(ep);
265
266 if (dbg_entry)
267 show_entry(ep);
268 } END_FOR_EACH_PTR(sym);
269 }
270
collect_info_from_args(const int argc,char * const * const argv)271 static void collect_info_from_args(const int argc, char *const *const argv)
272 {
273 int i;
274
275 for (i = 0; i < argc; i++) {
276 if (!strcmp("-DLTPLIB", argv[i]))
277 tu_kind = LTP_LIB;
278 }
279 }
280
main(int argc,char ** argv)281 int main(int argc, char **argv)
282 {
283 struct string_list *filelist = NULL;
284 char *file;
285
286 Waddress_space = 0;
287 Wbitwise = 0;
288 Wcast_truncate = 0;
289 Wcontext = 0;
290 Wdecl = 0;
291 Wexternal_function_has_definition = 0;
292 Wflexible_array_array = 0;
293 Wimplicit_int = 0;
294 Wint_to_pointer_cast = 0;
295 Wmemcpy_max_count = 0;
296 Wnon_pointer_null = 0;
297 Wone_bit_signed_bitfield = 0;
298 Woverride_init = 0;
299 Wpointer_to_int_cast = 0;
300 Wvla = 0;
301
302 do_output = 0;
303
304 collect_info_from_args(argc, argv);
305
306 process_symbols(sparse_initialize(argc, argv, &filelist));
307 FOR_EACH_PTR(filelist, file) {
308 process_symbols(sparse(file));
309 } END_FOR_EACH_PTR(file);
310
311 report_stats();
312 return 0;
313 }
314