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

source code of gcc/analyzer/call-details.cc