1/*
2 * Copyright (C) 2012 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/*
19 * This is for functions like:
20 *
21 * int foo(int *x)
22 * {
23 * 	if (*x == 42) {
24 *		*x = 0;
25 *		return 1;
26 *	}
27 * 	return 0;
28 * }
29 *
30 * If we return 1 that means the value of *x has been set to 0.  If we return
31 * 0 then we have left *x alone.
32 *
33 */
34
35#include "scope.h"
36#include "smatch.h"
37#include "smatch_slist.h"
38#include "smatch_extra.h"
39
40static int my_id;
41
42static struct smatch_state *unmatched_state(struct sm_state *sm)
43{
44	return alloc_estate_empty();
45}
46
47static int parent_is_set(const char *name, struct symbol *sym, struct smatch_state *state)
48{
49	struct expression *faked;
50	char *left_name;
51	int ret = 0;
52	int len;
53
54	if (!__in_fake_assign)
55		return 0;
56	if (!is_whole_rl(estate_rl(state)))
57		return 0;
58	if (get_state(my_id, name, sym))
59		return 0;
60
61	faked = get_faked_expression();
62	if (!faked)
63		return 0;
64	if ((faked->type == EXPR_PREOP || faked->type == EXPR_POSTOP) &&
65	    (faked->op == SPECIAL_INCREMENT || faked->op == SPECIAL_DECREMENT)) {
66		faked = strip_expr(faked->unop);
67		if (faked->type == EXPR_SYMBOL)
68			return 1;
69		return 0;
70	}
71	if (faked->type != EXPR_ASSIGNMENT)
72		return 0;
73
74	left_name = expr_to_var(faked->left);
75	if (!left_name)
76		return 0;
77
78	len = strlen(left_name);
79	if (strncmp(name, left_name, len) == 0 && name[len] == '-')
80		ret = 1;
81	free_string(left_name);
82
83	return ret;
84}
85
86static void extra_mod_hook(const char *name, struct symbol *sym, struct expression *expr, struct smatch_state *state)
87{
88	if (parent_is_set(name, sym, state))
89		return;
90	if (get_param_num_from_sym(sym) < 0)
91		return;
92	set_state(my_id, name, sym, state);
93}
94
95/*
96 * This function is is a dirty hack because extra_mod_hook is giving us a NULL
97 *  sym instead of a vsl.
98 */
99static void match_array_assignment(struct expression *expr)
100{
101	struct expression *array, *offset;
102	char *name;
103	struct symbol *sym;
104	struct range_list *rl;
105	sval_t sval;
106	char buf[256];
107
108	if (__in_fake_assign)
109		return;
110
111	if (!is_array(expr->left))
112		return;
113	array = get_array_base(expr->left);
114	offset = get_array_offset(expr->left);
115
116	/* These are handled by extra_mod_hook() */
117	if (get_value(offset, &sval))
118		return;
119	name = expr_to_var_sym(array, &sym);
120	if (!name || !sym)
121		goto free;
122	if (get_param_num_from_sym(sym) < 0)
123		goto free;
124	get_absolute_rl(expr->right, &rl);
125	rl = cast_rl(get_type(expr->left), rl);
126
127	snprintf(buf, sizeof(buf), "*%s", name);
128	set_state(my_id, buf, sym, alloc_estate_rl(rl));
129free:
130	free_string(name);
131}
132
133static char *get_two_dots(const char *name)
134{
135	static char buf[80];
136	int i, cnt = 0;
137
138	for (i = 0; i < sizeof(buf); i++) {
139		if (name[i] == '.') {
140			cnt++;
141			if (cnt >= 2) {
142				buf[i] = '\0';
143				return buf;
144			}
145		}
146		buf[i] = name[i];
147	}
148	return NULL;
149}
150
151/*
152 * This relies on the fact that these states are stored so that
153 * foo->bar is before foo->bar->baz.
154 */
155static int parent_set(struct string_list *list, const char *name)
156{
157	char *tmp;
158	int len;
159	int ret;
160
161	FOR_EACH_PTR(list, tmp) {
162		len = strlen(tmp);
163		ret = strncmp(tmp, name, len);
164		if (ret < 0)
165			continue;
166		if (ret > 0)
167			return 0;
168		if (name[len] == '-')
169			return 1;
170	} END_FOR_EACH_PTR(tmp);
171
172	return 0;
173}
174
175static void print_return_value_param_helper(int return_id, char *return_ranges, struct expression *expr, int limit)
176{
177	struct sm_state *sm;
178	struct smatch_state *extra;
179	int param;
180	struct range_list *rl;
181	const char *param_name;
182	struct string_list *set_list = NULL;
183	char *math_str;
184	char buf[256];
185	char two_dot[80] = "";
186	int count = 0;
187
188	FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
189		if (!estate_rl(sm->state))
190			continue;
191		extra = __get_state(SMATCH_EXTRA, sm->name, sm->sym);
192		if (extra) {
193			rl = rl_intersection(estate_rl(sm->state), estate_rl(extra));
194			if (!rl)
195				continue;
196		} else {
197			rl = estate_rl(sm->state);
198		}
199
200		param = get_param_num_from_sym(sm->sym);
201		if (param < 0)
202			continue;
203		param_name = get_param_name(sm);
204		if (!param_name)
205			continue;
206		if (strcmp(param_name, "$") == 0) {
207			insert_string(&set_list, (char *)sm->name);
208			continue;
209		}
210		if (is_recursive_member(param_name)) {
211			insert_string(&set_list, (char *)sm->name);
212			continue;
213		}
214
215		if (is_ignored_kernel_data(param_name)) {
216			insert_string(&set_list, (char *)sm->name);
217			continue;
218		}
219		if (limit) {
220			char *new = get_two_dots(param_name);
221
222			if (new) {
223				if (strcmp(new, two_dot) == 0)
224					continue;
225				strncpy(two_dot, new, sizeof(two_dot));
226				sql_insert_return_states(return_id, return_ranges,
227					 PARAM_SET, param, new, "s64min-s64max");
228				continue;
229			}
230		}
231
232		math_str = get_value_in_terms_of_parameter_math_var_sym(sm->name, sm->sym);
233		if (math_str) {
234			snprintf(buf, sizeof(buf), "%s[%s]", show_rl(rl), math_str);
235			insert_string(&set_list, (char *)sm->name);
236			sql_insert_return_states(return_id, return_ranges,
237					param_has_filter_data(sm) ? PARAM_ADD : PARAM_SET,
238					param, param_name, buf);
239			continue;
240		}
241
242		/* no useful information here. */
243		if (is_whole_rl(rl) && parent_set(set_list, sm->name))
244			continue;
245		insert_string(&set_list, (char *)sm->name);
246
247		sql_insert_return_states(return_id, return_ranges,
248					 param_has_filter_data(sm) ? PARAM_ADD : PARAM_SET,
249					 param, param_name, show_rl(rl));
250		if (limit && ++count > limit)
251			break;
252
253	} END_FOR_EACH_SM(sm);
254
255	free_ptr_list((struct ptr_list **)&set_list);
256}
257
258static void print_return_value_param(int return_id, char *return_ranges, struct expression *expr)
259{
260	print_return_value_param_helper(return_id, return_ranges, expr, 0);
261}
262
263void print_limited_param_set(int return_id, char *return_ranges, struct expression *expr)
264{
265	print_return_value_param_helper(return_id, return_ranges, expr, 1000);
266}
267
268static int possibly_empty(struct sm_state *sm)
269{
270	struct sm_state *tmp;
271
272	FOR_EACH_PTR(sm->possible, tmp) {
273		if (strcmp(tmp->name, "") == 0)
274			return 1;
275	} END_FOR_EACH_PTR(tmp);
276	return 0;
277}
278
279int param_was_set_var_sym(const char *name, struct symbol *sym)
280{
281	struct sm_state *sm;
282	char buf[80];
283	int len, i;
284
285	if (!name)
286		return 0;
287
288	len = strlen(name);
289	if (len >= sizeof(buf))
290		len = sizeof(buf) - 1;
291
292	for (i = 0; i <= len; i++) {
293		if (name[i] != '-' && name[i] != '\0')
294			continue;
295
296		memcpy(buf, name, i);
297		buf[i] = '\0';
298
299		sm = get_sm_state(my_id, buf, sym);
300		if (!sm)
301			continue;
302		if (possibly_empty(sm))
303			continue;
304		return 1;
305	}
306
307	if (name[0] == '*')
308		return param_was_set_var_sym(name + 1, sym);
309
310	return 0;
311}
312
313int param_was_set(struct expression *expr)
314{
315	char *name;
316	struct symbol *sym;
317	int ret = 0;
318
319	name = expr_to_var_sym(expr, &sym);
320	if (!name || !sym)
321		goto free;
322
323	ret = param_was_set_var_sym(name, sym);
324free:
325	free_string(name);
326	return ret;
327}
328
329void register_param_set(int id)
330{
331	my_id = id;
332
333	set_dynamic_states(my_id);
334	add_extra_mod_hook(&extra_mod_hook);
335	add_hook(match_array_assignment, ASSIGNMENT_HOOK);
336	add_unmatched_state_hook(my_id, &unmatched_state);
337	add_merge_hook(my_id, &merge_estates);
338	add_split_return_callback(&print_return_value_param);
339}
340
341