1/*
2 * Copyright (C) 2014 Oracle.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
16 */
17
18#define _GNU_SOURCE
19#include <string.h>
20#include "smatch.h"
21#include "smatch_slist.h"
22
23static int my_id;
24
25STATE(checked);
26STATE(modified);
27
28struct stree *to_check;
29
30static struct statement *get_cur_stmt(void)
31{
32	return last_ptr_list((struct ptr_list *)big_statement_stack);
33}
34
35static void set_modified(struct sm_state *sm, struct expression *mod_expr)
36{
37	set_state(my_id, sm->name, sm->sym, &modified);
38}
39
40static struct expression *strip_condition(struct expression *expr)
41{
42	expr = strip_expr(expr);
43
44	if (expr->type == EXPR_PREOP && expr->op == '!')
45		return strip_condition(expr->unop);
46
47	if (expr->type == EXPR_COMPARE &&
48	    (expr->op == SPECIAL_EQUAL ||
49	     expr->op == SPECIAL_NOTEQUAL)) {
50		if (expr_is_zero(expr->left))
51			return strip_condition(expr->right);
52		if (expr_is_zero(expr->right))
53			return strip_condition(expr->left);
54	}
55
56	return expr;
57}
58
59static int conditions_match(struct expression *cond, struct expression *prev)
60{
61	prev = strip_condition(prev);
62
63	if (prev == cond)
64		return 1;
65
66	if (prev->type == EXPR_LOGICAL) {
67		if (conditions_match(cond, prev->left) ||
68		    conditions_match(cond, prev->right))
69			return 1;
70	}
71
72	return 0;
73}
74
75/*
76 * People like to do "if (foo) { ... } else if (!foo) { ... }".  Don't
77 * complain when they do that even though it is nonsense.
78 */
79static int is_obvious_else(struct expression *cond)
80{
81	struct statement *parent;
82	struct expression *prev;
83
84	if (!get_cur_stmt())
85		return 0;
86	parent = get_cur_stmt()->parent;
87	if (!parent)
88		return 0;
89
90	if (parent->type != STMT_IF)
91		return 0;
92
93	if (!parent->if_false)
94		return 0;
95	if (parent->if_false != get_cur_stmt())
96		return 0;
97
98	prev = strip_condition(parent->if_conditional);
99
100	return conditions_match(cond, prev);
101}
102
103static int name_means_synchronize(const char *name)
104{
105	if (!name)
106		return 0;
107
108	if (strcasestr(name, "wait"))
109		return 1;
110	if (strcasestr(name, "down"))
111		return 1;
112	if (strcasestr(name, "lock") && !strcasestr(name, "unlock"))
113		return 1;
114	if (strcasestr(name, "delay"))
115		return 1;
116	if (strcasestr(name, "schedule"))
117		return 1;
118	if (strcmp(name, "smp_rmb") == 0)
119		return 1;
120	if (strcmp(name, "mb") == 0)
121		return 1;
122	if (strcmp(name, "barrier") == 0)
123		return 1;
124	return 0;
125}
126
127static int previous_statement_was_synchronize(void)
128{
129	struct statement *stmt;
130	struct position pos;
131	struct position prev_pos;
132	char *ident;
133
134	if (!__cur_stmt)
135		return 0;
136
137	if (__prev_stmt) {
138		prev_pos = __prev_stmt->pos;
139		prev_pos.line -= 3;
140	} else {
141		prev_pos = __cur_stmt->pos;
142		prev_pos.line -= 5;
143	}
144
145	FOR_EACH_PTR_REVERSE(big_statement_stack, stmt) {
146		if (stmt->pos.line < prev_pos.line)
147			return 0;
148		pos = stmt->pos;
149		ident = get_macro_name(pos);
150		if (name_means_synchronize(ident))
151			return 1;
152		ident = pos_ident(pos);
153		if (!ident)
154			continue;
155		if (strcmp(ident, "if") == 0) {
156			pos.pos += 4;
157			ident = pos_ident(pos);
158			if (!ident)
159				continue;
160		}
161		if (name_means_synchronize(ident))
162			return 1;
163	} END_FOR_EACH_PTR_REVERSE(stmt);
164	return 0;
165}
166
167static void match_condition(struct expression *expr)
168{
169	struct smatch_state *state;
170	sval_t dummy;
171	char *name;
172
173	if (inside_loop())
174		return;
175
176	if (get_value(expr, &dummy))
177		return;
178
179	if (get_macro_name(expr->pos))
180		return;
181
182	state = get_stored_condition(expr);
183	if (!state || !state->data)
184		return;
185	if (get_macro_name(((struct expression *)state->data)->pos))
186		return;
187
188	/*
189	 * we allow double checking for NULL because people do this all the time
190	 * and trying to stop them is a losers' battle.
191	 */
192	if (is_pointer(expr) && implied_condition_true(expr))
193		return;
194
195	if (definitely_inside_loop()) {
196		struct symbol *sym;
197
198		if (__inline_fn)
199			return;
200
201		name = expr_to_var_sym(expr, &sym);
202		if (!name)
203			return;
204		set_state_expr(my_id, expr, &checked);
205		set_state_stree(&to_check, my_id, name, sym, &checked);
206		free_string(name);
207		return;
208	}
209
210	if (is_obvious_else(state->data))
211		return;
212
213	/*
214	 * It's common to test something, then take a lock and test if it is
215	 * still true.
216	 */
217	if (previous_statement_was_synchronize())
218		return;
219
220	name = expr_to_str(expr);
221	sm_warning("we tested '%s' before and it was '%s'", name, state->name);
222	free_string(name);
223}
224
225int get_check_line(struct sm_state *sm)
226{
227	struct sm_state *tmp;
228
229	FOR_EACH_PTR(sm->possible, tmp) {
230		if (tmp->state == &checked)
231			return tmp->line;
232	} END_FOR_EACH_PTR(tmp);
233
234	return get_lineno();
235}
236
237static void after_loop(struct statement *stmt)
238{
239	struct sm_state *check, *sm;
240
241	if (!stmt || stmt->type != STMT_ITERATOR)
242		return;
243	if (definitely_inside_loop())
244		return;
245	if (__inline_fn)
246		return;
247
248	FOR_EACH_SM(to_check, check) {
249		continue;
250		sm = get_sm_state(my_id, check->name, check->sym);
251		continue;
252		if (!sm)
253			continue;
254		if (slist_has_state(sm->possible, &modified))
255			continue;
256
257		sm_printf("%s:%d %s() ", get_filename(), get_check_line(sm), get_function());
258		sm_printf("warn: we tested '%s' already\n", check->name);
259	} END_FOR_EACH_SM(check);
260
261	free_stree(&to_check);
262}
263
264static void match_func_end(struct symbol *sym)
265{
266	if (__inline_fn)
267		return;
268	if (to_check)
269		sm_msg("debug: odd...  found an function without an end.");
270	free_stree(&to_check);
271}
272
273void check_double_checking(int id)
274{
275	my_id = id;
276
277	if (!option_spammy)
278		return;
279
280	add_hook(&match_condition, CONDITION_HOOK);
281	add_modification_hook(my_id, &set_modified);
282	add_hook(after_loop, STMT_HOOK_AFTER);
283	add_hook(&match_func_end, AFTER_FUNC_HOOK);
284}
285