xref: /illumos-gate/usr/src/tools/smatch/src/smatch_return_to_param.c (revision c65ebfc7045424bd04a6c7719a27b0ad3399ad54)
1 /*
2  * Copyright (C) 2017 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 smatch_extra.c to use.  It sort of like check_assigned_expr.c but
20  * more limited.  Say a function returns "64min-s64max[$0->data]" and the caller
21  * does "struct whatever *p = get_data(dev);" then we want to record that p is
22  * now the same as "dev->data".  Then if we update "p->foo" it means we can
23  * update "dev->data->foo" as well.
24  *
25  */
26 
27 #include "smatch.h"
28 #include "smatch_slist.h"
29 #include "smatch_extra.h"
30 
31 extern int check_assigned_expr_id;
32 static int my_id;
33 static int link_id;
34 
35 static struct smatch_state *alloc_my_state(const char *name, struct symbol *sym)
36 {
37 	struct smatch_state *state;
38 
39 	state = __alloc_smatch_state(0);
40 	state->name = alloc_sname(name);
41 	state->data = sym;
42 	return state;
43 }
44 
45 static void undef(struct sm_state *sm, struct expression *mod_expr)
46 {
47 	if (__in_fake_parameter_assign)
48 		return;
49 	set_state(my_id, sm->name, sm->sym, &undefined);
50 }
51 
52 char *map_call_to_other_name_sym(const char *name, struct symbol *sym, struct symbol **new_sym)
53 {
54 	struct smatch_state *state;
55 	int skip;
56 	char buf[256];
57 
58 	/* skip 'foo->'.  This was checked in the caller. */
59 	skip = strlen(sym->ident->name) + 2;
60 
61 	state = get_state(my_id, sym->ident->name, sym);
62 	if (!state || !state->data)
63 		return NULL;
64 
65 	snprintf(buf, sizeof(buf), "%s->%s", state->name, name + skip);
66 	*new_sym = state->data;
67 	return alloc_string(buf);
68 }
69 
70 static char *map_my_state_long_to_short(struct sm_state *sm, const char *name, struct symbol *sym, struct symbol **new_sym, bool stack)
71 {
72 	int len;
73 	char buf[256];
74 
75 	if (sm->state->data != sym)
76 		return NULL;
77 	len = strlen(sm->state->name);
78 	if (strncmp(name, sm->state->name, len) != 0)
79 		return NULL;
80 
81 	if (name[len] == '.')
82 		return NULL;
83 	if (!stack && name[len] != '-')
84 		return NULL;
85 	snprintf(buf, sizeof(buf), "%s%s", sm->name, name + len);
86 	*new_sym = sm->sym;
87 	return alloc_string(buf);
88 }
89 
90 static char *map_assignment_long_to_short(struct sm_state *sm, const char *name, struct symbol *sym, struct symbol **new_sym, bool stack)
91 {
92 	struct expression *orig_expr;
93 	struct symbol *orig_sym;
94 	int len;
95 	char buf[256];
96 
97 	orig_expr = sm->state->data;
98 	if (!orig_expr)
99 		return NULL;
100 
101 	/*
102 	 * Say we have an assignment like:
103 	 *     foo->bar->my_ptr = my_ptr;
104 	 * We still expect the function to carry on using "my_ptr" as the
105 	 * shorter name.  That's not a long to short mapping.
106 	 *
107 	 */
108 	if (orig_expr->type == EXPR_SYMBOL)
109 		return NULL;
110 
111 	orig_sym = expr_to_sym(orig_expr);
112 	if (!orig_sym)
113 		return NULL;
114 	if (sym != orig_sym)
115 		return NULL;
116 
117 	len = strlen(sm->state->name);
118 	if (strncmp(name, sm->state->name, len) != 0)
119 		return NULL;
120 
121 	if (name[len] == '.')
122 		return NULL;
123 	if (!stack && name[len] != '-')
124 		return NULL;
125 	snprintf(buf, sizeof(buf), "%s%s", sm->name, name + len);
126 	*new_sym = sm->sym;
127 	return alloc_string(buf);
128 }
129 
130 /*
131  * Normally, we expect people to consistently refer to variables by the shortest
132  * name.  So they use "b->a" instead of "foo->bar.a" when both point to the
133  * same memory location.  However, when we're dealing across function boundaries
134  * then sometimes we pass frob(foo) which sets foo->bar.a.  In that case, we
135  * translate it to the shorter name.  Smatch extra updates the shorter name,
136  * which in turn updates the longer name.
137  *
138  */
139 static char *map_long_to_short_name_sym_helper(const char *name, struct symbol *sym, struct symbol **new_sym, bool stack)
140 {
141 	char *ret;
142 	struct sm_state *sm;
143 
144 	*new_sym = NULL;
145 
146 	FOR_EACH_SM(__get_cur_stree(), sm) {
147 		if (sm->owner == my_id) {
148 			ret = map_my_state_long_to_short(sm, name, sym, new_sym, stack);
149 			if (ret)
150 				return ret;
151 			continue;
152 		}
153 		if (sm->owner == check_assigned_expr_id) {
154 			ret = map_assignment_long_to_short(sm, name, sym, new_sym, stack);
155 			if (ret)
156 				return ret;
157 			continue;
158 		}
159 	} END_FOR_EACH_SM(sm);
160 
161 	return NULL;
162 }
163 
164 char *map_long_to_short_name_sym(const char *name, struct symbol *sym, struct symbol **new_sym)
165 {
166 	return map_long_to_short_name_sym_helper(name, sym, new_sym, 1);
167 }
168 
169 char *map_long_to_short_name_sym_nostack(const char *name, struct symbol *sym, struct symbol **new_sym)
170 {
171 	return map_long_to_short_name_sym_helper(name, sym, new_sym, 0);
172 }
173 
174 char *map_call_to_param_name_sym(struct expression *expr, struct symbol **sym)
175 {
176 	char *name;
177 	struct symbol *start_sym;
178 	struct smatch_state *state;
179 
180 	*sym = NULL;
181 
182 	name = expr_to_str_sym(expr, &start_sym);
183 	if (!name)
184 		return NULL;
185 	if (expr->type == EXPR_CALL)
186 		start_sym = expr_to_sym(expr->fn);
187 
188 	state = get_state(my_id, name, start_sym);
189 	free_string(name);
190 	if (!state || !state->data)
191 		return NULL;
192 
193 	*sym = state->data;
194 	return alloc_string(state->name);
195 }
196 
197 static void store_mapping_helper(char *left_name, struct symbol *left_sym, struct expression *call, const char *return_string)
198 {
199 	const char *p = return_string;
200 	char *close;
201 	int param;
202 	struct expression *arg, *new;
203 	char *right_name;
204 	struct symbol *right_sym;
205 	char buf[256];
206 
207 	while (*p && *p != '[')
208 		p++;
209 	if (!*p)
210 		return;
211 	p++;
212 	if (*p != '$')
213 		return;
214 
215 	snprintf(buf, sizeof(buf), "%s", p);
216 	close = strchr(buf, ']');
217 	if (!close)
218 		return;
219 	*close = '\0';
220 
221 	param = atoi(buf + 1);
222 	arg = get_argument_from_call_expr(call->args, param);
223 	if (!arg)
224 		return;
225 
226 	new = gen_expression_from_key(arg, buf);
227 	if (!new)
228 		return;
229 
230 	right_name = expr_to_var_sym(new, &right_sym);
231 	if (!right_name || !right_sym)
232 		goto free;
233 
234 	set_state(my_id, left_name, left_sym, alloc_my_state(right_name, right_sym));
235 	store_link(link_id, right_name, right_sym, left_name, left_sym);
236 
237 free:
238 	free_string(right_name);
239 }
240 
241 void __add_return_to_param_mapping(struct expression *expr, const char *return_string)
242 {
243 	struct expression *call;
244 	char *left_name = NULL;
245 	struct symbol *left_sym;
246 
247 	if (expr->type == EXPR_ASSIGNMENT) {
248 		left_name = expr_to_var_sym(expr->left, &left_sym);
249 		if (!left_name || !left_sym)
250 			goto free;
251 
252 		call = strip_expr(expr->right);
253 		if (call->type != EXPR_CALL)
254 			goto free;
255 
256 		store_mapping_helper(left_name, left_sym, call, return_string);
257 		goto free;
258 	}
259 
260 	if (expr->type == EXPR_CALL &&
261 	    expr_get_parent_stmt(expr) &&
262 	    expr_get_parent_stmt(expr)->type == STMT_RETURN) {
263 		call = strip_expr(expr);
264 		left_sym = expr_to_sym(call->fn);
265 		if (!left_sym)
266 			return;
267 		left_name = expr_to_str(call);
268 		if (!left_name)
269 			return;
270 
271 		store_mapping_helper(left_name, left_sym, call, return_string);
272 		goto free;
273 
274 	}
275 
276 free:
277 	free_string(left_name);
278 }
279 
280 void register_return_to_param(int id)
281 {
282 	my_id = id;
283 	add_modification_hook(my_id, &undef);
284 }
285 
286 void register_return_to_param_links(int id)
287 {
288 	link_id = id;
289 	set_up_link_functions(my_id, link_id);
290 }
291 
292