]>
Commit | Line | Data |
---|---|---|
861c917a | 1 | /* Helper class for handling a call with specific arguments. |
a945c346 | 2 | Copyright (C) 2020-2024 Free Software Foundation, Inc. |
861c917a DM |
3 | Contributed by David Malcolm <dmalcolm@redhat.com>. |
4 | ||
5 | This file is part of GCC. | |
6 | ||
7 | GCC is free software; you can redistribute it and/or modify it | |
8 | under the terms of the GNU General Public License as published by | |
9 | the Free Software Foundation; either version 3, or (at your option) | |
10 | any later version. | |
11 | ||
12 | GCC is distributed in the hope that it will be useful, but | |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
18 | along with GCC; see the file COPYING3. If not see | |
19 | <http://www.gnu.org/licenses/>. */ | |
20 | ||
21 | #include "config.h" | |
22 | #define INCLUDE_MEMORY | |
23 | #include "system.h" | |
24 | #include "coretypes.h" | |
25 | #include "tree.h" | |
26 | #include "function.h" | |
27 | #include "basic-block.h" | |
28 | #include "gimple.h" | |
29 | #include "diagnostic-core.h" | |
30 | #include "analyzer/analyzer.h" | |
31 | #include "analyzer/analyzer-logging.h" | |
32 | #include "diagnostic.h" | |
33 | #include "tree-diagnostic.h" /* for default_tree_printer. */ | |
34 | #include "gimple-pretty-print.h" | |
35 | #include "analyzer/region-model.h" | |
36 | #include "analyzer/call-details.h" | |
034d99e8 | 37 | #include "analyzer/ranges.h" |
021077b9 DM |
38 | #include "stringpool.h" |
39 | #include "attribs.h" | |
034d99e8 | 40 | #include "make-unique.h" |
861c917a DM |
41 | |
42 | #if ENABLE_ANALYZER | |
43 | ||
44 | namespace ana { | |
45 | ||
46 | /* class call_details. */ | |
47 | ||
48 | /* call_details's ctor. */ | |
49 | ||
50 | call_details::call_details (const gcall *call, region_model *model, | |
51 | region_model_context *ctxt) | |
52 | : m_call (call), m_model (model), m_ctxt (ctxt), | |
53 | m_lhs_type (NULL_TREE), m_lhs_region (NULL) | |
54 | { | |
55 | m_lhs_type = NULL_TREE; | |
56 | if (tree lhs = gimple_call_lhs (call)) | |
57 | { | |
58 | m_lhs_region = model->get_lvalue (lhs, ctxt); | |
59 | m_lhs_type = TREE_TYPE (lhs); | |
60 | } | |
61 | } | |
62 | ||
3b691e01 DM |
63 | /* call_details's ctor: copy CD, but override the context, |
64 | using CTXT instead. */ | |
65 | ||
66 | call_details::call_details (const call_details &cd, | |
67 | region_model_context *ctxt) | |
68 | { | |
69 | *this = cd; | |
70 | m_ctxt = ctxt; | |
71 | } | |
72 | ||
861c917a DM |
73 | /* Get the manager from m_model. */ |
74 | ||
75 | region_model_manager * | |
76 | call_details::get_manager () const | |
77 | { | |
78 | return m_model->get_manager (); | |
79 | } | |
80 | ||
81 | /* Get any logger associated with this object. */ | |
82 | ||
83 | logger * | |
84 | call_details::get_logger () const | |
85 | { | |
86 | if (m_ctxt) | |
87 | return m_ctxt->get_logger (); | |
88 | else | |
89 | return NULL; | |
90 | } | |
91 | ||
92 | /* Get any uncertainty_t associated with the region_model_context. */ | |
93 | ||
94 | uncertainty_t * | |
95 | call_details::get_uncertainty () const | |
96 | { | |
97 | if (m_ctxt) | |
98 | return m_ctxt->get_uncertainty (); | |
99 | else | |
100 | return NULL; | |
101 | } | |
102 | ||
103 | /* If the callsite has a left-hand-side region, set it to RESULT | |
104 | and return true. | |
105 | Otherwise do nothing and return false. */ | |
106 | ||
107 | bool | |
108 | call_details::maybe_set_lhs (const svalue *result) const | |
109 | { | |
110 | gcc_assert (result); | |
111 | if (m_lhs_region) | |
112 | { | |
113 | m_model->set_value (m_lhs_region, result, m_ctxt); | |
114 | return true; | |
115 | } | |
116 | else | |
117 | return false; | |
118 | } | |
119 | ||
73da34a5 DM |
120 | /* Return true if CD is known to be a call to a function with |
121 | __attribute__((const)). */ | |
122 | ||
123 | static bool | |
124 | const_fn_p (const call_details &cd) | |
125 | { | |
126 | tree fndecl = cd.get_fndecl_for_call (); | |
127 | if (!fndecl) | |
128 | return false; | |
129 | gcc_assert (DECL_P (fndecl)); | |
130 | return TREE_READONLY (fndecl); | |
131 | } | |
132 | ||
133 | /* If this CD is known to be a call to a function with | |
134 | __attribute__((const)), attempt to get a const_fn_result_svalue | |
135 | based on the arguments, or return NULL otherwise. */ | |
136 | ||
137 | static const svalue * | |
138 | maybe_get_const_fn_result (const call_details &cd) | |
139 | { | |
140 | if (!const_fn_p (cd)) | |
141 | return NULL; | |
142 | ||
143 | unsigned num_args = cd.num_args (); | |
144 | if (num_args > const_fn_result_svalue::MAX_INPUTS) | |
145 | /* Too many arguments. */ | |
146 | return NULL; | |
147 | ||
148 | auto_vec<const svalue *> inputs (num_args); | |
149 | for (unsigned arg_idx = 0; arg_idx < num_args; arg_idx++) | |
150 | { | |
151 | const svalue *arg_sval = cd.get_arg_svalue (arg_idx); | |
152 | if (!arg_sval->can_have_associated_state_p ()) | |
153 | return NULL; | |
154 | inputs.quick_push (arg_sval); | |
155 | } | |
156 | ||
157 | region_model_manager *mgr = cd.get_manager (); | |
158 | const svalue *sval | |
159 | = mgr->get_or_create_const_fn_result_svalue (cd.get_lhs_type (), | |
160 | cd.get_fndecl_for_call (), | |
161 | inputs); | |
162 | return sval; | |
163 | } | |
164 | ||
165 | /* Look for attribute "alloc_size" on the called function and, if found, | |
166 | return a symbolic value of type size_type_node for the allocation size | |
167 | based on the call's parameters. | |
168 | Otherwise, return null. */ | |
169 | ||
170 | static const svalue * | |
171 | get_result_size_in_bytes (const call_details &cd) | |
172 | { | |
173 | const tree attr = cd.lookup_function_attribute ("alloc_size"); | |
174 | if (!attr) | |
175 | return nullptr; | |
176 | ||
177 | const tree atval_1 = TREE_VALUE (attr); | |
178 | if (!atval_1) | |
179 | return nullptr; | |
180 | ||
181 | unsigned argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval_1)) - 1; | |
182 | if (cd.num_args () <= argidx1) | |
183 | return nullptr; | |
184 | ||
185 | const svalue *sval_arg1 = cd.get_arg_svalue (argidx1); | |
186 | ||
187 | if (const tree atval_2 = TREE_CHAIN (atval_1)) | |
188 | { | |
189 | /* Two arguments. */ | |
190 | unsigned argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval_2)) - 1; | |
191 | if (cd.num_args () <= argidx2) | |
192 | return nullptr; | |
193 | const svalue *sval_arg2 = cd.get_arg_svalue (argidx2); | |
194 | /* TODO: ideally we shouldn't need this cast here; | |
195 | see PR analyzer/110902. */ | |
196 | return cd.get_manager ()->get_or_create_cast | |
197 | (size_type_node, | |
198 | cd.get_manager ()->get_or_create_binop (size_type_node, | |
199 | MULT_EXPR, | |
200 | sval_arg1, sval_arg2)); | |
201 | } | |
202 | else | |
203 | /* Single argument. */ | |
204 | return cd.get_manager ()->get_or_create_cast (size_type_node, sval_arg1); | |
205 | } | |
206 | ||
207 | /* If this call has an LHS, assign a value to it based on attributes | |
208 | of the function: | |
209 | - if __attribute__((const)), use a const_fn_result_svalue, | |
210 | - if __attribute__((malloc)), use a heap-allocated region with | |
211 | unknown content | |
212 | - otherwise, use a conjured_svalue. | |
213 | ||
214 | If __attribute__((alloc_size), set the dynamic extents on the region | |
215 | pointed to. */ | |
216 | ||
217 | void | |
218 | call_details::set_any_lhs_with_defaults () const | |
219 | { | |
220 | if (!m_lhs_region) | |
221 | return; | |
222 | ||
223 | const svalue *sval = maybe_get_const_fn_result (*this); | |
224 | if (!sval) | |
225 | { | |
226 | region_model_manager *mgr = get_manager (); | |
227 | if (lookup_function_attribute ("malloc")) | |
228 | { | |
229 | const region *new_reg | |
230 | = m_model->get_or_create_region_for_heap_alloc (NULL, m_ctxt); | |
231 | m_model->mark_region_as_unknown (new_reg, NULL); | |
232 | sval = mgr->get_ptr_svalue (get_lhs_type (), new_reg); | |
233 | } | |
234 | else | |
235 | /* For the common case of functions without __attribute__((const)), | |
236 | use a conjured value, and purge any prior state involving that | |
237 | value (in case this is in a loop). */ | |
238 | sval = get_or_create_conjured_svalue (m_lhs_region); | |
239 | if (const svalue *size_in_bytes = get_result_size_in_bytes (*this)) | |
240 | { | |
241 | const region *reg | |
242 | = m_model->deref_rvalue (sval, NULL_TREE, m_ctxt, false); | |
243 | m_model->set_dynamic_extents (reg, size_in_bytes, m_ctxt); | |
244 | } | |
245 | } | |
246 | maybe_set_lhs (sval); | |
247 | } | |
248 | ||
861c917a DM |
249 | /* Return the number of arguments used by the call statement. */ |
250 | ||
251 | unsigned | |
252 | call_details::num_args () const | |
253 | { | |
254 | return gimple_call_num_args (m_call); | |
255 | } | |
256 | ||
257 | /* Return true if argument IDX is a size_t (or compatible with it). */ | |
258 | ||
259 | bool | |
260 | call_details::arg_is_size_p (unsigned idx) const | |
261 | { | |
262 | return types_compatible_p (get_arg_type (idx), size_type_node); | |
263 | } | |
264 | ||
265 | /* Get the location of the call statement. */ | |
266 | ||
267 | location_t | |
268 | call_details::get_location () const | |
269 | { | |
270 | return m_call->location; | |
271 | } | |
272 | ||
273 | /* Get argument IDX at the callsite as a tree. */ | |
274 | ||
275 | tree | |
276 | call_details::get_arg_tree (unsigned idx) const | |
277 | { | |
278 | return gimple_call_arg (m_call, idx); | |
279 | } | |
280 | ||
281 | /* Get the type of argument IDX. */ | |
282 | ||
283 | tree | |
284 | call_details::get_arg_type (unsigned idx) const | |
285 | { | |
286 | return TREE_TYPE (gimple_call_arg (m_call, idx)); | |
287 | } | |
288 | ||
289 | /* Get argument IDX at the callsite as an svalue. */ | |
290 | ||
291 | const svalue * | |
292 | call_details::get_arg_svalue (unsigned idx) const | |
293 | { | |
294 | tree arg = get_arg_tree (idx); | |
295 | return m_model->get_rvalue (arg, m_ctxt); | |
296 | } | |
297 | ||
e7b26744 | 298 | /* If argument IDX's svalue at the callsite is of pointer type, |
299 | return the region it points to. | |
300 | Otherwise return NULL. */ | |
301 | ||
302 | const region * | |
303 | call_details::deref_ptr_arg (unsigned idx) const | |
304 | { | |
305 | const svalue *ptr_sval = get_arg_svalue (idx); | |
306 | return m_model->deref_rvalue (ptr_sval, get_arg_tree (idx), m_ctxt); | |
307 | } | |
308 | ||
861c917a DM |
309 | /* Attempt to get the string literal for argument IDX, or return NULL |
310 | otherwise. | |
311 | For use when implementing "__analyzer_*" functions that take | |
312 | string literals. */ | |
313 | ||
314 | const char * | |
315 | call_details::get_arg_string_literal (unsigned idx) const | |
316 | { | |
317 | const svalue *str_arg = get_arg_svalue (idx); | |
318 | if (const region *pointee = str_arg->maybe_get_region ()) | |
319 | if (const string_region *string_reg = pointee->dyn_cast_string_region ()) | |
320 | { | |
321 | tree string_cst = string_reg->get_string_cst (); | |
322 | return TREE_STRING_POINTER (string_cst); | |
323 | } | |
324 | return NULL; | |
325 | } | |
326 | ||
327 | /* Attempt to get the fndecl used at this call, if known, or NULL_TREE | |
328 | otherwise. */ | |
329 | ||
330 | tree | |
331 | call_details::get_fndecl_for_call () const | |
332 | { | |
333 | return m_model->get_fndecl_for_call (m_call, m_ctxt); | |
334 | } | |
335 | ||
336 | /* Dump a multiline representation of this call to PP. */ | |
337 | ||
338 | void | |
339 | call_details::dump_to_pp (pretty_printer *pp, bool simple) const | |
340 | { | |
341 | pp_string (pp, "gcall: "); | |
342 | pp_gimple_stmt_1 (pp, m_call, 0 /* spc */, TDF_NONE /* flags */); | |
343 | pp_newline (pp); | |
344 | pp_string (pp, "return region: "); | |
345 | if (m_lhs_region) | |
346 | m_lhs_region->dump_to_pp (pp, simple); | |
347 | else | |
348 | pp_string (pp, "NULL"); | |
349 | pp_newline (pp); | |
350 | for (unsigned i = 0; i < gimple_call_num_args (m_call); i++) | |
351 | { | |
352 | const svalue *arg_sval = get_arg_svalue (i); | |
353 | pp_printf (pp, "arg %i: ", i); | |
354 | arg_sval->dump_to_pp (pp, simple); | |
355 | pp_newline (pp); | |
356 | } | |
357 | } | |
358 | ||
359 | /* Dump a multiline representation of this call to stderr. */ | |
360 | ||
361 | DEBUG_FUNCTION void | |
362 | call_details::dump (bool simple) const | |
363 | { | |
364 | pretty_printer pp; | |
365 | pp_format_decoder (&pp) = default_tree_printer; | |
366 | pp_show_color (&pp) = pp_show_color (global_dc->printer); | |
367 | pp.buffer->stream = stderr; | |
368 | dump_to_pp (&pp, simple); | |
369 | pp_flush (&pp); | |
370 | } | |
371 | ||
372 | /* Get a conjured_svalue for this call for REG, | |
373 | and purge any state already relating to that conjured_svalue. */ | |
374 | ||
375 | const svalue * | |
376 | call_details::get_or_create_conjured_svalue (const region *reg) const | |
377 | { | |
378 | region_model_manager *mgr = m_model->get_manager (); | |
379 | return mgr->get_or_create_conjured_svalue (reg->get_type (), m_call, reg, | |
380 | conjured_purge (m_model, m_ctxt)); | |
381 | } | |
382 | ||
021077b9 DM |
383 | /* Look for a function attribute with name ATTR_NAME on the called |
384 | function (or on its type). | |
385 | Return the attribute if one is found, otherwise return NULL_TREE. */ | |
386 | ||
387 | tree | |
388 | call_details::lookup_function_attribute (const char *attr_name) const | |
389 | { | |
390 | tree allocfntype; | |
391 | if (tree fndecl = get_fndecl_for_call ()) | |
392 | allocfntype = TREE_TYPE (fndecl); | |
393 | else | |
394 | allocfntype = gimple_call_fntype (m_call); | |
395 | ||
396 | if (!allocfntype) | |
397 | return NULL_TREE; | |
398 | ||
399 | return lookup_attribute (attr_name, TYPE_ATTRIBUTES (allocfntype)); | |
400 | } | |
401 | ||
bbdc0e0d DM |
402 | void |
403 | call_details::check_for_null_terminated_string_arg (unsigned arg_idx) const | |
404 | { | |
405 | check_for_null_terminated_string_arg (arg_idx, false, nullptr); | |
406 | } | |
407 | ||
fe97f09a DM |
408 | const svalue * |
409 | call_details:: | |
410 | check_for_null_terminated_string_arg (unsigned arg_idx, | |
bbdc0e0d | 411 | bool include_terminator, |
fe97f09a | 412 | const svalue **out_sval) const |
325f9e88 DM |
413 | { |
414 | region_model *model = get_model (); | |
bbdc0e0d DM |
415 | return model->check_for_null_terminated_string_arg (*this, |
416 | arg_idx, | |
417 | include_terminator, | |
418 | out_sval); | |
325f9e88 DM |
419 | } |
420 | ||
034d99e8 DM |
421 | /* A subclass of pending_diagnostic for complaining about overlapping |
422 | buffers. */ | |
423 | ||
424 | class overlapping_buffers | |
425 | : public pending_diagnostic_subclass<overlapping_buffers> | |
426 | { | |
427 | public: | |
428 | overlapping_buffers (tree fndecl) | |
429 | : m_fndecl (fndecl) | |
430 | { | |
431 | } | |
432 | ||
433 | const char *get_kind () const final override | |
434 | { | |
435 | return "overlapping_buffers"; | |
436 | } | |
437 | ||
438 | bool operator== (const overlapping_buffers &other) const | |
439 | { | |
440 | return m_fndecl == other.m_fndecl; | |
441 | } | |
442 | ||
443 | int get_controlling_option () const final override | |
444 | { | |
445 | return OPT_Wanalyzer_overlapping_buffers; | |
446 | } | |
447 | ||
12b67d1e | 448 | bool emit (diagnostic_emission_context &ctxt) final override |
034d99e8 DM |
449 | { |
450 | auto_diagnostic_group d; | |
451 | ||
12b67d1e DM |
452 | bool warned = ctxt.warn ("overlapping buffers passed as arguments to %qD", |
453 | m_fndecl); | |
034d99e8 DM |
454 | |
455 | // TODO: draw a picture? | |
456 | ||
457 | if (warned) | |
458 | inform (DECL_SOURCE_LOCATION (m_fndecl), | |
459 | "the behavior of %qD is undefined for overlapping buffers", | |
460 | m_fndecl); | |
461 | ||
462 | return warned; | |
463 | } | |
464 | ||
465 | label_text describe_final_event (const evdesc::final_event &ev) final override | |
466 | { | |
467 | return ev.formatted_print | |
468 | ("overlapping buffers passed as arguments to %qD", | |
469 | m_fndecl); | |
470 | } | |
471 | ||
472 | private: | |
473 | tree m_fndecl; | |
474 | }; | |
475 | ||
476 | ||
477 | /* Check if the buffers pointed to by arguments ARG_IDX_A and ARG_IDX_B | |
478 | (zero-based) overlap, when considering them both to be of size | |
479 | NUM_BYTES_READ_SVAL. | |
480 | ||
481 | If they do overlap, complain to the context. */ | |
482 | ||
483 | void | |
484 | call_details::complain_about_overlap (unsigned arg_idx_a, | |
485 | unsigned arg_idx_b, | |
486 | const svalue *num_bytes_read_sval) const | |
487 | { | |
488 | region_model_context *ctxt = get_ctxt (); | |
489 | if (!ctxt) | |
490 | return; | |
491 | ||
492 | region_model *model = get_model (); | |
493 | region_model_manager *mgr = model->get_manager (); | |
494 | ||
495 | const svalue *arg_a_ptr_sval = get_arg_svalue (arg_idx_a); | |
496 | if (arg_a_ptr_sval->get_kind () == SK_UNKNOWN) | |
497 | return; | |
498 | const region *arg_a_reg = model->deref_rvalue (arg_a_ptr_sval, | |
499 | get_arg_tree (arg_idx_a), | |
500 | ctxt); | |
501 | const svalue *arg_b_ptr_sval = get_arg_svalue (arg_idx_b); | |
502 | if (arg_b_ptr_sval->get_kind () == SK_UNKNOWN) | |
503 | return; | |
504 | const region *arg_b_reg = model->deref_rvalue (arg_b_ptr_sval, | |
505 | get_arg_tree (arg_idx_b), | |
506 | ctxt); | |
507 | if (arg_a_reg->get_base_region () != arg_b_reg->get_base_region ()) | |
508 | return; | |
509 | ||
510 | /* Are they within NUM_BYTES_READ_SVAL of each other? */ | |
511 | symbolic_byte_range byte_range_a (arg_a_reg->get_offset (mgr), | |
512 | num_bytes_read_sval, | |
513 | *mgr); | |
514 | symbolic_byte_range byte_range_b (arg_b_reg->get_offset (mgr), | |
515 | num_bytes_read_sval, | |
516 | *mgr); | |
517 | if (!byte_range_a.intersection (byte_range_b, *model).is_true ()) | |
518 | return; | |
519 | ||
520 | ctxt->warn (make_unique<overlapping_buffers> (get_fndecl_for_call ())); | |
521 | } | |
522 | ||
861c917a DM |
523 | } // namespace ana |
524 | ||
525 | #endif /* #if ENABLE_ANALYZER */ |