xref: /illumos-gate/usr/src/tools/smatch/src/check_atomic_inc_dec.c (revision 1f5207b7604fb44407eb4342aff613f7c4508508)
1 /*
2  * Copyright (C) 2016 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 #include "smatch.h"
19 #include "smatch_extra.h"
20 #include "smatch_slist.h"
21 
22 static int my_id;
23 
24 STATE(inc);
25 STATE(orig);
26 STATE(dec);
27 
28 static void db_inc_dec(struct expression *expr, int param, const char *key, const char *value, int inc_dec)
29 {
30 	struct smatch_state *start_state;
31 	struct expression *arg;
32 	char *name;
33 	struct symbol *sym;
34 
35 	while (expr->type == EXPR_ASSIGNMENT)
36 		expr = strip_expr(expr->right);
37 	if (expr->type != EXPR_CALL)
38 		return;
39 
40 	arg = get_argument_from_call_expr(expr->args, param);
41 	if (!arg)
42 		return;
43 
44 	name = get_variable_from_key(arg, key, &sym);
45 	if (!name || !sym)
46 		goto free;
47 
48 	start_state = get_state(my_id, name, sym);
49 
50 	if (inc_dec == ATOMIC_INC) {
51 //		if (start_state == &inc)
52 //			sm_error("XXX double increment '%s'", name);
53 		set_state(my_id, name, sym, &inc);
54 	} else {
55 //		if (start_state == &dec)
56 //			sm_error("XXX double decrement '%s'", name);
57 		if (start_state == &inc)
58 			set_state(my_id, name, sym, &orig);
59 		else
60 			set_state(my_id, name, sym, &dec);
61 	}
62 
63 free:
64 	free_string(name);
65 }
66 
67 static void db_inc(struct expression *expr, int param, char *key, char *value)
68 {
69 	db_inc_dec(expr, param, key, value, ATOMIC_INC);
70 }
71 
72 static void db_dec(struct expression *expr, int param, char *key, char *value)
73 {
74 	db_inc_dec(expr, param, key, value, ATOMIC_DEC);
75 }
76 
77 static void match_atomic_inc(const char *fn, struct expression *expr, void *_unused)
78 {
79 	db_inc_dec(expr, 0, "$->counter", "", ATOMIC_INC);
80 }
81 
82 static void match_atomic_dec(const char *fn, struct expression *expr, void *_unused)
83 {
84 	db_inc_dec(expr, 0, "$->counter", "", ATOMIC_DEC);
85 }
86 
87 static void match_atomic_add(const char *fn, struct expression *expr, void *_unused)
88 {
89 	struct expression *amount;
90 	sval_t sval;
91 
92 	amount = get_argument_from_call_expr(expr->args, 0);
93 	if (get_implied_value(amount, &sval) && sval_is_negative(sval)) {
94 		db_inc_dec(expr, 1, "$->counter", "", ATOMIC_DEC);
95 		return;
96 	}
97 
98 	db_inc_dec(expr, 1, "$->counter", "", ATOMIC_INC);
99 }
100 
101 static void match_atomic_sub(const char *fn, struct expression *expr, void *_unused)
102 {
103 	db_inc_dec(expr, 1, "$->counter", "", ATOMIC_DEC);
104 }
105 
106 static void refcount_inc(const char *fn, struct expression *expr, void *param)
107 {
108 	db_inc_dec(expr, PTR_INT(param), "$->ref.counter", "", ATOMIC_INC);
109 }
110 
111 static void refcount_dec(const char *fn, struct expression *expr, void *param)
112 {
113 	db_inc_dec(expr, PTR_INT(param), "$->ref.counter", "", ATOMIC_DEC);
114 }
115 
116 static void match_return_info(int return_id, char *return_ranges, struct expression *expr)
117 {
118 	struct sm_state *sm;
119 	const char *param_name;
120 	int param;
121 
122 	FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
123 		if (sm->state != &inc &&
124 		    sm->state != &dec)
125 			continue;
126 		param = get_param_num_from_sym(sm->sym);
127 		if (param < 0)
128 			continue;
129 		param_name = get_param_name(sm);
130 		if (!param_name)
131 			continue;
132 		sql_insert_return_states(return_id, return_ranges,
133 					 (sm->state == &inc) ? ATOMIC_INC : ATOMIC_DEC,
134 					 param, param_name, "");
135 	} END_FOR_EACH_SM(sm);
136 }
137 
138 enum {
139 	NEGATIVE, ZERO, POSITIVE,
140 };
141 
142 static int success_fail_positive(struct range_list *rl)
143 {
144 	if (!rl)
145 		return ZERO;
146 
147 	if (sval_is_negative(rl_min(rl)))
148 		return NEGATIVE;
149 
150 	if (rl_min(rl).value == 0)
151 		return ZERO;
152 
153 	return POSITIVE;
154 }
155 
156 static void check_counter(const char *name, struct symbol *sym)
157 {
158 	struct range_list *inc_lines = NULL;
159 	struct range_list *dec_lines = NULL;
160 	int inc_buckets[3] = {};
161 	struct stree *stree;
162 	struct sm_state *return_sm;
163 	struct sm_state *sm;
164 	sval_t line = sval_type_val(&int_ctype, 0);
165 
166 	FOR_EACH_PTR(get_all_return_strees(), stree) {
167 		return_sm = get_sm_state_stree(stree, RETURN_ID, "return_ranges", NULL);
168 		if (!return_sm)
169 			continue;
170 		line.value = return_sm->line;
171 
172 		sm = get_sm_state_stree(stree, my_id, name, sym);
173 		if (!sm)
174 			continue;
175 
176 		if (sm->state != &inc && sm->state != &dec)
177 			continue;
178 
179 		if (sm->state == &inc) {
180 			add_range(&inc_lines, line, line);
181 			inc_buckets[success_fail_positive(estate_rl(return_sm->state))] = 1;
182 		}
183 		if (sm->state == &dec)
184 			add_range(&dec_lines, line, line);
185 	} END_FOR_EACH_PTR(stree);
186 
187 	if (inc_buckets[NEGATIVE] &&
188 	    inc_buckets[ZERO]) {
189 		// sm_warning("XXX '%s' not decremented on lines: %s.", name, show_rl(inc_lines));
190 	}
191 
192 }
193 
194 static void match_check_missed(struct symbol *sym)
195 {
196 	struct sm_state *sm;
197 
198 	FOR_EACH_MY_SM(my_id, get_all_return_states(), sm) {
199 		check_counter(sm->name, sm->sym);
200 	} END_FOR_EACH_SM(sm);
201 }
202 
203 int on_atomic_dec_path(void)
204 {
205 	struct sm_state *sm;
206 
207 	FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
208 		if (sm->state == &dec)
209 			return 1;
210 	} END_FOR_EACH_SM(sm);
211 
212 	return 0;
213 }
214 
215 int was_inced(const char *name, struct symbol *sym)
216 {
217 	return get_state(my_id, name, sym) == &inc;
218 }
219 
220 void check_atomic_inc_dec(int id)
221 {
222 	my_id = id;
223 
224 	if (option_project != PROJ_KERNEL)
225 		return;
226 
227 	select_return_states_hook(ATOMIC_INC, &db_inc);
228 	select_return_states_hook(ATOMIC_DEC, &db_dec);
229 	add_function_hook("atomic_inc_return", &match_atomic_inc, NULL);
230 	add_function_hook("atomic_add_return", &match_atomic_add, NULL);
231 	add_function_hook("atomic_sub_return", &match_atomic_sub, NULL);
232 	add_function_hook("atomic_sub_and_test", &match_atomic_sub, NULL);
233 	add_function_hook("atomic_dec_and_test", &match_atomic_dec, NULL);
234 	add_function_hook("_atomic_dec_and_lock", &match_atomic_dec, NULL);
235 	add_function_hook("atomic_dec", &match_atomic_dec, NULL);
236 	add_function_hook("atomic_long_inc", &match_atomic_inc, NULL);
237 	add_function_hook("atomic_long_dec", &match_atomic_dec, NULL);
238 	add_function_hook("atomic_inc", &match_atomic_inc, NULL);
239 	add_function_hook("atomic_sub", &match_atomic_sub, NULL);
240 	add_split_return_callback(match_return_info);
241 
242 	add_function_hook("refcount_add_not_zero", &refcount_inc, INT_PTR(1));
243 	add_function_hook("refcount_inc_not_zero", &refcount_inc, INT_PTR(0));
244 	add_function_hook("refcount_sub_and_test", &refcount_dec, INT_PTR(1));
245 	add_function_hook("refcount_dec_and_test", &refcount_dec, INT_PTR(1));
246 
247 	add_hook(&match_check_missed, END_FUNC_HOOK);
248 }
249