Line data Source code
1 : /* Implementation of <stdarg.h> within analyzer.
2 : Copyright (C) 2022-2026 Free Software Foundation, Inc.
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 "analyzer/common.h"
22 :
23 : #include "analyzer/analyzer-logging.h"
24 : #include "analyzer/sm.h"
25 : #include "analyzer/pending-diagnostic.h"
26 : #include "analyzer/call-string.h"
27 : #include "analyzer/program-point.h"
28 : #include "analyzer/store.h"
29 : #include "analyzer/region-model.h"
30 : #include "analyzer/program-state.h"
31 : #include "analyzer/checker-path.h"
32 : #include "analyzer/supergraph.h"
33 : #include "analyzer/diagnostic-manager.h"
34 : #include "analyzer/exploded-graph.h"
35 : #include "analyzer/call-details.h"
36 :
37 : #if ENABLE_ANALYZER
38 :
39 : namespace ana {
40 :
41 : /* Implementation of <stdarg.h> within analyzer.
42 :
43 : Objectives:
44 : - detection of interprocedural type errors involving va_arg
45 : - tracking of symbolic values interprocedurally from variadic call
46 : through to va_arg unpacking
47 : - detection of missing va_end
48 : - detection of va_arg outside of a va_start/va_end pair
49 : - detection of uses of a va_list after the frame in containing the
50 : va_start has returned
51 :
52 : The analyzer runs *before* the "stdarg" and "lower_vaarg" gimple
53 : passes, which have target-dependent effects.
54 :
55 : This file implements a state machine on svalues for tracking when
56 : va_start has been called, so that we can detect missing va_end,
57 : and misplaced va_arg, etc.
58 : To do this requires an svalue that can have state, so we implement va_start
59 : by creating a stack-allocated region, and use a pointer to that region
60 : as the svalue that has state.
61 :
62 : We call this stack-allocated region the "impl_reg". Allocating it on
63 : the stack ensures that it is invalidated when the frame containing
64 : the va_start returns, leading to
65 : -Wanalyzer-use-of-pointer-in-stale-stack-frame on attempts to use such
66 : a va_list.
67 :
68 : To track svalues from variadic calls interprocedurally, we implement
69 : variadic arguments via new child regions of the callee's frame_region,
70 : var_arg_region, each one representing a storage slot for one of the
71 : variadic arguments, accessed by index.
72 :
73 : We have:
74 :
75 : stack frame:
76 : va_list: &impl_reg
77 : 'impl_reg': pointer to next var_arg_region
78 : var_arg_region for arg 0
79 : ...
80 : var_arg_region for arg N-1
81 :
82 : Hence given test_1 in stdarg-1.c, at the call to:
83 :
84 : __analyzer_called_by_test_1 (int placeholder, ...);
85 :
86 : here:
87 :
88 : __analyzer_called_by_test_1 (42, "foo", 1066, '@');
89 :
90 : we push this frame for the called function:
91 : clusters within frame: ‘__analyzer_called_by_test_1’@2
92 : cluster for: placeholder: (int)42
93 : cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0): &"foo" (TOUCHED)
94 : cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 1): (int)1066 (TOUCHED)
95 : cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 2): (int)64 (TOUCHED)
96 : where the called function's frame has been populated with both the value
97 : of the regular argument "placeholder", and with values for 3 variadic
98 : arguments.
99 :
100 : At the call to
101 : va_start (ap, placeholder);
102 : we allocate a region ALLOCA_REGION for ap to point to, populate that
103 : region with the address of variadic argument 0, and set sm-state of
104 : &ALLOCA_REGION to "started":
105 : clusters within frame: ‘__analyzer_called_by_test_1’@2
106 : cluster for: placeholder: (int)42
107 : cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0): &"foo" (TOUCHED)
108 : cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 1): (int)1066 (TOUCHED)
109 : cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 2): (int)64 (TOUCHED)
110 : cluster for: ap: &ALLOCA_REGION
111 : cluster for: ALLOCA_REGION: &VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0) (TOUCHED)
112 : va_list:
113 : 0x4c83700: &ALLOCA_REGION: started
114 :
115 : At each call to
116 : va_arg (ap, TYPE);
117 : we can look within *ap, locate the region holding the next variadic
118 : argument to be extracted, extract the svalue, and advance the index
119 : by effectively updating *ap.
120 :
121 : At the va_end, we can set &ALLOCA_REGION's state to "ended".
122 :
123 : The various __builtin_va_* accept ap by pointer, so we have e.g.:
124 :
125 : __builtin_va_start (&ap, [...]);
126 :
127 : except for the 2nd param of __builtin_va_copy, where the type
128 : is already target-dependent (see the discussion of get_va_copy_arg
129 : below). */
130 :
131 : /* Get a tree for diagnostics.
132 : Typically we have "&ap", but it will make more sense to
133 : the user as just "ap", so strip off the ADDR_EXPR. */
134 :
135 : static tree
136 1190 : get_va_list_diag_arg (tree va_list_tree)
137 : {
138 1190 : if (TREE_CODE (va_list_tree) == ADDR_EXPR)
139 967 : va_list_tree = TREE_OPERAND (va_list_tree, 0);
140 1190 : return va_list_tree;
141 : }
142 :
143 : /* Get argument ARG_IDX of va_copy.
144 :
145 : builtin-types.def has:
146 : DEF_PRIMITIVE_TYPE (BT_VALIST_ARG, va_list_arg_type_node)
147 :
148 : and c_common_nodes_and_builtins initializes va_list_arg_type_node
149 : based on whether TREE_CODE (va_list_type_node) is of ARRAY_TYPE or
150 : not, giving either one or zero levels of indirection.
151 :
152 : Alternatively we could be dealing with __builtin_ms_va_copy or
153 : __builtin_sysv_va_copy.
154 :
155 : Handle this by looking at the types of the argument in question. */
156 :
157 : static const svalue *
158 104 : get_va_copy_arg (const region_model *model,
159 : region_model_context *ctxt,
160 : const gcall &call,
161 : unsigned arg_idx)
162 : {
163 104 : tree arg = gimple_call_arg (&call, arg_idx);
164 104 : const svalue *arg_sval = model->get_rvalue (arg, ctxt);
165 104 : if (const svalue *cast = arg_sval->maybe_undo_cast ())
166 10 : arg_sval = cast;
167 104 : if (TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE
168 104 : && TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == ARRAY_TYPE)
169 : {
170 : /* va_list_arg_type_node is a pointer to a va_list;
171 : return *ARG_SVAL. */
172 86 : const region *src_reg = model->deref_rvalue (arg_sval, arg, ctxt);
173 86 : const svalue *src_reg_sval = model->get_store_value (src_reg, ctxt);
174 86 : if (const svalue *cast = src_reg_sval->maybe_undo_cast ())
175 72 : src_reg_sval = cast;
176 86 : return src_reg_sval;
177 : }
178 : else
179 : {
180 : /* va_list_arg_type_node is a va_list; return ARG_SVAL. */
181 : return arg_sval;
182 : }
183 : }
184 :
185 : namespace {
186 :
187 : /* A state machine for tracking the state of a va_list, so that
188 : we can enforce that each va_start is paired with a va_end,
189 : and va_arg only happens within a va_start/va_end pair.
190 : Specifically, this tracks the state of the &ALLOCA_BUFFER
191 : that va_start/va_copy allocate. */
192 :
193 : class va_list_state_machine : public state_machine
194 : {
195 : public:
196 : va_list_state_machine (logger *logger);
197 :
198 1422517 : bool inherited_state_p () const final override { return false; }
199 :
200 : bool on_stmt (sm_context &sm_ctxt,
201 : const gimple *stmt) const final override;
202 :
203 1421266 : bool can_purge_p (state_t s) const final override
204 : {
205 1421266 : return s != m_started;
206 : }
207 :
208 : std::unique_ptr<pending_diagnostic>
209 : on_leak (tree var,
210 : const program_state *old_state,
211 : const program_state *new_state) const final override;
212 :
213 : /* State for a va_list that is the result of a va_start or va_copy. */
214 : state_t m_started;
215 :
216 : /* State for a va_list that has had va_end called on it. */
217 : state_t m_ended;
218 :
219 : private:
220 : void on_va_start (sm_context &sm_ctxt, const gcall &call) const;
221 : void on_va_copy (sm_context &sm_ctxt, const gcall &call) const;
222 : void on_va_arg (sm_context &sm_ctxt, const gcall &call) const;
223 : void on_va_end (sm_context &sm_ctxt, const gcall &call) const;
224 : void check_for_ended_va_list (sm_context &sm_ctxt,
225 : const svalue *arg,
226 : const char *usage_fnname) const;
227 : };
228 :
229 : /* va_list_state_machine's ctor. */
230 :
231 3377 : va_list_state_machine::va_list_state_machine (logger *logger)
232 : : state_machine ("va_list", logger),
233 6754 : m_started (add_state ("started")),
234 3377 : m_ended (add_state ("ended"))
235 : {
236 3377 : }
237 :
238 : /* Implementation of the various "va_*" functions for
239 : va_list_state_machine. */
240 :
241 : bool
242 263888 : va_list_state_machine::on_stmt (sm_context &sm_ctxt,
243 : const gimple *stmt) const
244 : {
245 263888 : if (const gcall *call_stmt = dyn_cast <const gcall *> (stmt))
246 : {
247 49635 : const gcall &call = *call_stmt;
248 :
249 49635 : if (gimple_call_internal_p (call_stmt)
250 49635 : && gimple_call_internal_fn (call_stmt) == IFN_VA_ARG)
251 : {
252 727 : on_va_arg (sm_ctxt, call);
253 727 : return false;
254 : }
255 :
256 48908 : if (tree callee_fndecl = sm_ctxt.get_fndecl_for_call (call))
257 45837 : if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)
258 45837 : && gimple_builtin_call_types_compatible_p (&call, callee_fndecl))
259 22968 : switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
260 : {
261 : default:
262 : break;
263 :
264 365 : case BUILT_IN_VA_START:
265 365 : on_va_start (sm_ctxt, call);
266 365 : break;
267 :
268 45 : case BUILT_IN_VA_COPY:
269 45 : on_va_copy (sm_ctxt, call);
270 45 : break;
271 :
272 452 : case BUILT_IN_VA_END:
273 452 : on_va_end (sm_ctxt, call);
274 452 : break;
275 : }
276 : }
277 : return false;
278 : }
279 :
280 : /* Get the svalue for which va_list_state_machine holds state on argument ARG_
281 : IDX to CALL. */
282 :
283 : static const svalue *
284 1589 : get_stateful_arg (sm_context &sm_ctxt, const gcall &call, unsigned arg_idx)
285 : {
286 1589 : tree ap = gimple_call_arg (&call, arg_idx);
287 1589 : if (ap
288 1589 : && POINTER_TYPE_P (TREE_TYPE (ap)))
289 : {
290 1589 : if (const program_state *new_state = sm_ctxt.get_new_program_state ())
291 : {
292 1589 : const region_model *new_model = new_state->m_region_model;
293 1589 : const svalue *ptr_sval = new_model->get_rvalue (ap, nullptr);
294 1589 : const region *reg = new_model->deref_rvalue (ptr_sval, ap, nullptr);
295 1589 : const svalue *impl_sval = new_model->get_store_value (reg, nullptr);
296 1589 : if (const svalue *cast = impl_sval->maybe_undo_cast ())
297 1475 : impl_sval = cast;
298 1589 : return impl_sval;
299 : }
300 : }
301 : return nullptr;
302 : }
303 :
304 : /* Abstract class for diagnostics relating to va_list_state_machine. */
305 :
306 0 : class va_list_sm_diagnostic : public pending_diagnostic
307 : {
308 : public:
309 74 : bool subclass_equal_p (const pending_diagnostic &base_other) const override
310 : {
311 74 : const va_list_sm_diagnostic &other
312 : = (const va_list_sm_diagnostic &)base_other;
313 74 : return (m_ap_sval == other.m_ap_sval
314 74 : && same_tree_p (m_ap_tree, other.m_ap_tree));
315 : }
316 :
317 : bool
318 96 : describe_state_change (pretty_printer &pp,
319 : const evdesc::state_change &change) override
320 : {
321 96 : if (const char *fnname = maybe_get_fnname (change))
322 : {
323 96 : pp_printf (&pp, "%qs called here", fnname);
324 96 : return true;
325 : }
326 : return false;
327 : }
328 :
329 : diagnostics::paths::event::meaning
330 8 : get_meaning_for_state_change (const evdesc::state_change &change)
331 : const final override
332 : {
333 8 : if (change.m_new_state == m_sm.m_started)
334 8 : return diagnostics::paths::event::meaning (diagnostics::paths::event::verb::acquire,
335 8 : diagnostics::paths::event::noun::resource);
336 0 : if (change.m_new_state == m_sm.m_ended)
337 0 : return diagnostics::paths::event::meaning (diagnostics::paths::event::verb::release,
338 0 : diagnostics::paths::event::noun::resource);
339 0 : return diagnostics::paths::event::meaning ();
340 : }
341 :
342 : protected:
343 74 : va_list_sm_diagnostic (const va_list_state_machine &sm,
344 : const svalue *ap_sval, tree ap_tree)
345 74 : : m_sm (sm), m_ap_sval (ap_sval), m_ap_tree (ap_tree)
346 : {}
347 :
348 144 : static const char *maybe_get_fnname (const evdesc::state_change &change)
349 : {
350 144 : if (change.m_event.m_stmt)
351 144 : if (const gcall *call = as_a <const gcall *> (change.m_event.m_stmt))
352 144 : if (tree callee_fndecl = gimple_call_fndecl (call))
353 : {
354 144 : if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL))
355 144 : switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
356 : {
357 : case BUILT_IN_VA_START:
358 : return "va_start";
359 28 : case BUILT_IN_VA_COPY:
360 28 : return "va_copy";
361 24 : case BUILT_IN_VA_END:
362 24 : return "va_end";
363 : }
364 : }
365 : return nullptr;
366 : }
367 :
368 : const va_list_state_machine &m_sm;
369 : const svalue *m_ap_sval;
370 : tree m_ap_tree;
371 : };
372 :
373 : /* Concrete class for -Wanalyzer-va-list-use-after-va-end:
374 : complain about use of a va_list after va_end has been called on it. */
375 :
376 0 : class va_list_use_after_va_end : public va_list_sm_diagnostic
377 : {
378 : public:
379 12 : va_list_use_after_va_end (const va_list_state_machine &sm,
380 : const svalue *ap_sval, tree ap_tree,
381 : const char *usage_fnname)
382 12 : : va_list_sm_diagnostic (sm, ap_sval, ap_tree),
383 12 : m_usage_fnname (usage_fnname)
384 : {
385 : }
386 :
387 24 : int get_controlling_option () const final override
388 : {
389 24 : return OPT_Wanalyzer_va_list_use_after_va_end;
390 : }
391 :
392 : bool operator== (const va_list_use_after_va_end &other) const
393 : {
394 : return (va_list_sm_diagnostic::subclass_equal_p (other)
395 : && 0 == strcmp (m_usage_fnname, other.m_usage_fnname));
396 : }
397 :
398 12 : bool emit (diagnostic_emission_context &ctxt) final override
399 : {
400 12 : return ctxt.warn ("%qs after %qs", m_usage_fnname, "va_end");
401 : }
402 :
403 388 : const char *get_kind () const final override
404 : {
405 388 : return "va_list_use_after_va_end";
406 : }
407 :
408 : bool
409 48 : describe_state_change (pretty_printer &pp,
410 : const evdesc::state_change &change) final override
411 : {
412 48 : if (change.m_new_state == m_sm.m_ended)
413 24 : m_va_end_event = change.m_event_id;
414 48 : return va_list_sm_diagnostic::describe_state_change (pp, change);
415 : }
416 :
417 : bool
418 24 : describe_final_event (pretty_printer &pp,
419 : const evdesc::final_event &ev) final override
420 : {
421 24 : if (ev.m_expr)
422 : {
423 0 : if (m_va_end_event.known_p ())
424 0 : pp_printf (&pp,
425 : "%qs on %qE after %qs at %@",
426 : m_usage_fnname, ev.m_expr, "va_end", &m_va_end_event);
427 : else
428 0 : pp_printf (&pp,
429 : "%qs on %qE after %qs",
430 : m_usage_fnname, ev.m_expr, "va_end");
431 : }
432 : else
433 : {
434 24 : if (m_va_end_event.known_p ())
435 24 : pp_printf (&pp,
436 : "%qs after %qs at %@",
437 : m_usage_fnname, "va_end", &m_va_end_event);
438 : else
439 0 : pp_printf (&pp,
440 : "%qs after %qs",
441 : m_usage_fnname, "va_end");
442 : }
443 24 : return true;
444 : }
445 :
446 : private:
447 : diagnostics::paths::event_id_t m_va_end_event;
448 : const char *m_usage_fnname;
449 : };
450 :
451 : /* Concrete class for -Wanalyzer-va-list-leak:
452 : complain about a va_list in the "started" state that doesn't get after
453 : va_end called on it. */
454 :
455 : class va_list_leak : public va_list_sm_diagnostic
456 : {
457 : public:
458 62 : va_list_leak (const va_list_state_machine &sm,
459 : const svalue *ap_sval, tree ap_tree,
460 : const program_state *final_state)
461 62 : : va_list_sm_diagnostic (sm, ap_sval, ap_tree),
462 62 : m_start_event_fnname (nullptr),
463 62 : m_final_state ()
464 : {
465 62 : if (final_state)
466 62 : m_final_state = std::make_unique<program_state> (*final_state);
467 62 : }
468 :
469 86 : int get_controlling_option () const final override
470 : {
471 86 : return OPT_Wanalyzer_va_list_leak;
472 : }
473 :
474 : bool operator== (const va_list_leak &other) const
475 : {
476 : return va_list_sm_diagnostic::subclass_equal_p (other);
477 : }
478 :
479 24 : bool emit (diagnostic_emission_context &ctxt) final override
480 : {
481 24 : return ctxt.warn ("missing call to %qs", "va_end");
482 : }
483 :
484 822 : const char *get_kind () const final override { return "va_list_leak"; }
485 :
486 : bool
487 48 : describe_state_change (pretty_printer &pp,
488 : const evdesc::state_change &change) final override
489 : {
490 48 : if (change.m_new_state == m_sm.m_started)
491 : {
492 48 : m_start_event = change.m_event_id;
493 48 : m_start_event_fnname = maybe_get_fnname (change);
494 : }
495 48 : return va_list_sm_diagnostic::describe_state_change (pp, change);
496 : }
497 :
498 : bool
499 48 : describe_final_event (pretty_printer &pp,
500 : const evdesc::final_event &ev) final override
501 : {
502 48 : if (ev.m_expr)
503 : {
504 0 : if (m_start_event.known_p () && m_start_event_fnname)
505 0 : pp_printf (&pp,
506 : "missing call to %qs on %qE to match %qs at %@",
507 : "va_end", ev.m_expr, m_start_event_fnname, &m_start_event);
508 : else
509 0 : pp_printf (&pp,
510 : "missing call to %qs on %qE",
511 : "va_end", ev.m_expr);
512 : }
513 : else
514 : {
515 48 : if (m_start_event.known_p () && m_start_event_fnname)
516 48 : pp_printf (&pp,
517 : "missing call to %qs to match %qs at %@",
518 : "va_end", m_start_event_fnname, &m_start_event);
519 : else
520 0 : pp_printf (&pp,
521 : "missing call to %qs",
522 : "va_end");
523 : }
524 48 : return true;
525 : }
526 :
527 : const program_state *
528 24 : get_final_state () const final override
529 : {
530 24 : return m_final_state.get ();
531 : }
532 :
533 : private:
534 : diagnostics::paths::event_id_t m_start_event;
535 : const char *m_start_event_fnname;
536 : std::unique_ptr<program_state> m_final_state;
537 : };
538 :
539 : /* Update state machine for a "va_start" call. */
540 :
541 : void
542 365 : va_list_state_machine::on_va_start (sm_context &sm_ctxt,
543 : const gcall &call) const
544 : {
545 365 : const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
546 365 : if (arg)
547 : {
548 : /* Transition from start state to "started". */
549 365 : if (sm_ctxt.get_state (arg) == m_start)
550 365 : sm_ctxt.set_next_state (arg, m_started);
551 : }
552 365 : }
553 :
554 : /* Complain if ARG is in the "ended" state. */
555 :
556 : void
557 776 : va_list_state_machine::check_for_ended_va_list (sm_context &sm_ctxt,
558 : const svalue *arg,
559 : const char *usage_fnname) const
560 : {
561 776 : if (sm_ctxt.get_state (arg) == m_ended)
562 12 : sm_ctxt.warn (arg,
563 : std::make_unique<va_list_use_after_va_end>
564 12 : (*this, arg, NULL_TREE, usage_fnname));
565 776 : }
566 :
567 : /* Get the svalue with associated va_list_state_machine state for
568 : ARG_IDX of CALL to va_copy, if SM_CTXT supports this,
569 : or nullptr otherwise. */
570 :
571 : static const svalue *
572 45 : get_stateful_va_copy_arg (sm_context &sm_ctxt,
573 : const gcall &call,
574 : unsigned arg_idx)
575 : {
576 45 : if (const program_state *new_state = sm_ctxt.get_new_program_state ())
577 : {
578 45 : const region_model *new_model = new_state->m_region_model;
579 45 : const svalue *arg = get_va_copy_arg (new_model, nullptr, call, arg_idx);
580 45 : return arg;
581 : }
582 : return nullptr;
583 : }
584 :
585 : /* Update state machine for a "va_copy" call. */
586 :
587 : void
588 45 : va_list_state_machine::on_va_copy (sm_context &sm_ctxt,
589 : const gcall &call) const
590 : {
591 45 : const svalue *src_arg = get_stateful_va_copy_arg (sm_ctxt, call, 1);
592 45 : if (src_arg)
593 45 : check_for_ended_va_list (sm_ctxt, src_arg, "va_copy");
594 :
595 45 : const svalue *dst_arg = get_stateful_arg (sm_ctxt, call, 0);
596 45 : if (dst_arg)
597 : {
598 : /* Transition from start state to "started". */
599 45 : if (sm_ctxt.get_state (dst_arg) == m_start)
600 45 : sm_ctxt.set_next_state (dst_arg, m_started);
601 : }
602 45 : }
603 :
604 : /* Update state machine for a "va_arg" call. */
605 :
606 : void
607 727 : va_list_state_machine::on_va_arg (sm_context &sm_ctxt,
608 : const gcall &call) const
609 : {
610 727 : const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
611 727 : if (arg)
612 727 : check_for_ended_va_list (sm_ctxt, arg, "va_arg");
613 727 : }
614 :
615 : /* Update state machine for a "va_end" call. */
616 :
617 : void
618 452 : va_list_state_machine::on_va_end (sm_context &sm_ctxt,
619 : const gcall &call) const
620 : {
621 452 : const svalue *arg = get_stateful_arg (sm_ctxt, call, 0);
622 452 : if (arg)
623 : {
624 452 : state_t s = sm_ctxt.get_state (arg);
625 : /* Transition from "started" to "ended". */
626 452 : if (s == m_started)
627 407 : sm_ctxt.set_next_state (arg, m_ended);
628 45 : else if (s == m_ended)
629 4 : check_for_ended_va_list (sm_ctxt, arg, "va_end");
630 : }
631 452 : }
632 :
633 : /* Implementation of state_machine::on_leak vfunc for va_list_state_machine
634 : (for complaining about leaks of values in state 'started'). */
635 :
636 : std::unique_ptr<pending_diagnostic>
637 62 : va_list_state_machine::on_leak (tree var,
638 : const program_state *,
639 : const program_state *new_state) const
640 : {
641 62 : return std::make_unique<va_list_leak> (*this, nullptr, var, new_state);
642 : }
643 :
644 : } // anonymous namespace
645 :
646 : /* Internal interface to this file. */
647 :
648 : std::unique_ptr<state_machine>
649 3377 : make_va_list_state_machine (logger *logger)
650 : {
651 3377 : return std::make_unique<va_list_state_machine> (logger);
652 : }
653 :
654 : /* Handler for "__builtin_va_start". */
655 :
656 3377 : class kf_va_start : public known_function
657 : {
658 : public:
659 0 : bool matches_call_types_p (const call_details &) const final override
660 : {
661 0 : return true;
662 : }
663 : void impl_call_pre (const call_details &cd) const final override;
664 : };
665 :
666 : void
667 471 : kf_va_start::impl_call_pre (const call_details &cd) const
668 : {
669 471 : region_model *model = cd.get_model ();
670 471 : region_model_manager *mgr = cd.get_manager ();
671 471 : const svalue *out_ptr = cd.get_arg_svalue (0);
672 471 : const region *out_reg
673 471 : = model->deref_rvalue (out_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
674 471 : const frame_region *frame = model->get_current_frame ();
675 :
676 : /* "*out_ptr = &IMPL_REGION;". */
677 471 : const region *impl_reg = mgr->create_region_for_alloca (frame);
678 :
679 : /* We abuse the types here, since va_list_type isn't
680 : necessarily anything to do with a pointer. */
681 471 : const svalue *ptr_to_impl_reg = mgr->get_ptr_svalue (NULL_TREE, impl_reg);
682 471 : model->set_value (out_reg, ptr_to_impl_reg, cd.get_ctxt ());
683 :
684 471 : if (model->get_stack_depth () > 1)
685 : {
686 : /* The interprocedural case: the frame containing the va_start call
687 : will have been populated with any variadic aruguments.
688 : Initialize IMPL_REGION with a ptr to var_arg_region 0. */
689 240 : const region *init_var_arg_reg = mgr->get_var_arg_region (frame, 0);
690 240 : const svalue *ap_sval
691 240 : = mgr->get_ptr_svalue (NULL_TREE, init_var_arg_reg);
692 240 : model->set_value (impl_reg, ap_sval, cd.get_ctxt ());
693 : }
694 : else
695 : {
696 : /* The frame containing va_start is an entry-point to the analysis,
697 : so there won't be any specific var_arg_regions populated within it.
698 : Initialize IMPL_REGION as the UNKNOWN_SVALUE to avoid state
699 : explosions on repeated calls to va_arg. */
700 231 : const svalue *unknown_sval
701 231 : = mgr->get_or_create_unknown_svalue (NULL_TREE);
702 231 : model->set_value (impl_reg, unknown_sval, cd.get_ctxt ());
703 : }
704 471 : }
705 :
706 : /* Handler for "__builtin_va_copy". */
707 :
708 3377 : class kf_va_copy : public known_function
709 : {
710 : public:
711 0 : bool matches_call_types_p (const call_details &) const final override
712 : {
713 0 : return true;
714 : }
715 : void impl_call_pre (const call_details &cd) const final override;
716 : };
717 :
718 : void
719 59 : kf_va_copy::impl_call_pre (const call_details &cd) const
720 : {
721 59 : region_model *model = cd.get_model ();
722 59 : region_model_manager *mgr = cd.get_manager ();
723 59 : const svalue *out_dst_ptr = cd.get_arg_svalue (0);
724 59 : const svalue *in_va_list
725 59 : = get_va_copy_arg (model, cd.get_ctxt (), cd.get_call_stmt (), 1);
726 59 : in_va_list
727 59 : = model->check_for_poison (in_va_list,
728 : get_va_list_diag_arg (cd.get_arg_tree (1)),
729 : nullptr,
730 : cd.get_ctxt ());
731 :
732 59 : const region *out_dst_reg
733 59 : = model->deref_rvalue (out_dst_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
734 :
735 : /* "*out_dst_ptr = &NEW_IMPL_REGION;". */
736 59 : const region *new_impl_reg
737 59 : = mgr->create_region_for_alloca (model->get_current_frame ());
738 59 : const svalue *ptr_to_new_impl_reg
739 59 : = mgr->get_ptr_svalue (NULL_TREE, new_impl_reg);
740 59 : model->set_value (out_dst_reg, ptr_to_new_impl_reg, cd.get_ctxt ());
741 :
742 59 : if (const region *old_impl_reg = in_va_list->maybe_get_region ())
743 : {
744 : /* "(NEW_IMPL_REGION) = (OLD_IMPL_REGION);". */
745 45 : const svalue *existing_sval
746 45 : = model->get_store_value (old_impl_reg, cd.get_ctxt ());
747 45 : model->set_value (new_impl_reg, existing_sval, cd.get_ctxt ());
748 : }
749 59 : }
750 :
751 : /* Get the number of variadic arguments to CALLEE_FNDECL at CALL_STMT. */
752 :
753 : static int
754 34 : get_num_variadic_arguments (tree callee_fndecl,
755 : const gcall &call_stmt)
756 : {
757 34 : int num_positional = 0;
758 68 : for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm;
759 34 : iter_parm = DECL_CHAIN (iter_parm))
760 34 : num_positional++;
761 34 : return gimple_call_num_args (&call_stmt) - num_positional;
762 : }
763 :
764 : /* An abstract subclass of pending_diagnostic for diagnostics relating
765 : to bad va_arg invocations.
766 :
767 : This shows the number of variadic arguments at the call of interest.
768 : Ideally we'd also be able to highlight individual arguments, but
769 : that location information isn't generally available from the middle end. */
770 :
771 : class va_arg_diagnostic : public pending_diagnostic
772 : {
773 : public:
774 : /* Override of pending_diagnostic::add_call_event,
775 : adding a custom call_event subclass. */
776 42 : void add_call_event (const exploded_edge &eedge,
777 : const gcall &call_stmt,
778 : checker_path &emission_path) override
779 : {
780 : /* As per call_event, but show the number of variadic arguments
781 : in the call. */
782 0 : class va_arg_call_event : public call_event
783 : {
784 : public:
785 34 : va_arg_call_event (const exploded_edge &eedge,
786 : const event_loc_info &loc_info,
787 : int num_variadic_arguments)
788 34 : : call_event (eedge, loc_info),
789 34 : m_num_variadic_arguments (num_variadic_arguments)
790 : {
791 : }
792 :
793 68 : void print_desc (pretty_printer &pp) const override
794 : {
795 68 : pp_printf_n (&pp,
796 68 : m_num_variadic_arguments,
797 : "calling %qE from %qE with %i variadic argument",
798 : "calling %qE from %qE with %i variadic arguments",
799 : get_callee_fndecl (),
800 : get_caller_fndecl (),
801 68 : m_num_variadic_arguments);
802 68 : }
803 : private:
804 : int m_num_variadic_arguments;
805 : };
806 :
807 42 : const frame_region *frame_reg = m_var_arg_reg->get_frame_region ();
808 42 : const exploded_node *dst_node = eedge.m_dest;
809 42 : if (dst_node->get_state ().m_region_model->get_current_frame ()
810 : == frame_reg)
811 : {
812 34 : int num_variadic_arguments
813 34 : = get_num_variadic_arguments (dst_node->get_function ()->decl,
814 34 : call_stmt);
815 34 : emission_path.add_event
816 34 : (std::make_unique<va_arg_call_event>
817 34 : (eedge,
818 68 : event_loc_info (eedge.m_src),
819 : num_variadic_arguments));
820 : }
821 : else
822 8 : pending_diagnostic::add_call_event (eedge, call_stmt, emission_path);
823 42 : }
824 :
825 : protected:
826 51 : va_arg_diagnostic (tree va_list_tree, const var_arg_region *var_arg_reg)
827 51 : : m_va_list_tree (va_list_tree), m_var_arg_reg (var_arg_reg)
828 : {}
829 :
830 37 : bool subclass_equal_p (const pending_diagnostic &base_other) const override
831 : {
832 37 : const va_arg_diagnostic &other = (const va_arg_diagnostic &)base_other;
833 37 : return (same_tree_p (m_va_list_tree, other.m_va_list_tree)
834 37 : && m_var_arg_reg == other.m_var_arg_reg);
835 : }
836 :
837 : /* Get the number of arguments consumed so far from the va_list
838 : (*before* this va_arg call). */
839 102 : unsigned get_num_consumed () const
840 : {
841 102 : return m_var_arg_reg->get_index ();
842 : }
843 :
844 : /* Get a 1-based index of which variadic argument is being consumed. */
845 63 : unsigned get_variadic_index_for_diagnostic () const
846 : {
847 63 : return get_num_consumed () + 1;
848 : }
849 :
850 : /* User-readable expr for the va_list argument to va_arg. */
851 : tree m_va_list_tree;
852 :
853 : /* The region that the va_arg attempted to access. */
854 : const var_arg_region *m_var_arg_reg;
855 : };
856 :
857 : /* A subclass of pending_diagnostic for complaining about a type mismatch
858 : between the result of:
859 : va_arg (AP);
860 : and the type of the argument that was passed to the variadic call. */
861 :
862 : class va_arg_type_mismatch : public va_arg_diagnostic
863 : {
864 : public:
865 25 : va_arg_type_mismatch (tree va_list_tree, const var_arg_region *var_arg_reg,
866 : tree expected_type, tree actual_type)
867 25 : : va_arg_diagnostic (va_list_tree, var_arg_reg),
868 25 : m_expected_type (expected_type), m_actual_type (actual_type)
869 : {}
870 :
871 498 : const char *get_kind () const final override
872 : {
873 498 : return "va_arg_type_mismatch";
874 : }
875 :
876 24 : bool subclass_equal_p (const pending_diagnostic &base_other)
877 : const final override
878 : {
879 24 : if (!va_arg_diagnostic::subclass_equal_p (base_other))
880 : return false;
881 21 : const va_arg_type_mismatch &other
882 : = (const va_arg_type_mismatch &)base_other;
883 21 : return (same_tree_p (m_expected_type, other.m_expected_type)
884 21 : && same_tree_p (m_actual_type, other.m_actual_type));
885 : }
886 :
887 42 : int get_controlling_option () const final override
888 : {
889 42 : return OPT_Wanalyzer_va_arg_type_mismatch;
890 : }
891 :
892 21 : bool emit (diagnostic_emission_context &ctxt) final override
893 : {
894 : /* "CWE-686: Function Call With Incorrect Argument Type". */
895 21 : ctxt.add_cwe (686);
896 21 : bool warned
897 21 : = ctxt.warn ("%<va_arg%> expected %qT but received %qT"
898 : " for variadic argument %i of %qE",
899 : m_expected_type, m_actual_type,
900 : get_variadic_index_for_diagnostic (), m_va_list_tree);
901 21 : return warned;
902 : }
903 :
904 : bool
905 42 : describe_final_event (pretty_printer &pp,
906 : const evdesc::final_event &) final override
907 : {
908 42 : pp_printf (&pp,
909 : "%<va_arg%> expected %qT but received %qT"
910 : " for variadic argument %i of %qE",
911 : m_expected_type, m_actual_type,
912 : get_variadic_index_for_diagnostic (),
913 : m_va_list_tree);
914 42 : return true;
915 : }
916 :
917 : private:
918 : tree m_expected_type;
919 : tree m_actual_type;
920 : };
921 :
922 : /* A subclass of pending_diagnostic for complaining about a
923 : va_arg (AP);
924 : after all of the args in AP have been consumed. */
925 :
926 : class va_list_exhausted : public va_arg_diagnostic
927 : {
928 : public:
929 26 : va_list_exhausted (tree va_list_tree, const var_arg_region *var_arg_reg)
930 26 : : va_arg_diagnostic (va_list_tree, var_arg_reg)
931 : {}
932 :
933 134 : const char *get_kind () const final override
934 : {
935 134 : return "va_list_exhausted";
936 : }
937 :
938 26 : int get_controlling_option () const final override
939 : {
940 26 : return OPT_Wanalyzer_va_list_exhausted;
941 : }
942 :
943 13 : bool emit (diagnostic_emission_context &ctxt) final override
944 : {
945 : /* CWE-685: Function Call With Incorrect Number of Arguments. */
946 13 : ctxt.add_cwe (685);
947 13 : bool warned = ctxt.warn ("%qE has no more arguments (%i consumed)",
948 : m_va_list_tree, get_num_consumed ());
949 13 : return warned;
950 : }
951 :
952 : bool
953 26 : describe_final_event (pretty_printer &pp,
954 : const evdesc::final_event &) final override
955 : {
956 26 : pp_printf (&pp,
957 : "%qE has no more arguments (%i consumed)",
958 : m_va_list_tree, get_num_consumed ());
959 26 : return true;
960 : }
961 : };
962 :
963 : static bool
964 8 : representable_in_integral_type_p (const svalue &sval, const_tree type)
965 : {
966 8 : gcc_assert (INTEGRAL_TYPE_P (type));
967 :
968 8 : if (tree cst = sval.maybe_get_constant ())
969 8 : return wi::fits_to_tree_p (wi::to_wide (cst), type);
970 :
971 : return true;
972 : }
973 :
974 : /* Return true if it's OK to copy ARG_SVAL from ARG_TYPE to LHS_TYPE via
975 : va_arg (where argument promotion has already happened). */
976 :
977 : static bool
978 540 : va_arg_compatible_types_p (tree lhs_type, tree arg_type, const svalue &arg_sval)
979 : {
980 540 : if (compat_types_p (arg_type, lhs_type))
981 : return true;
982 :
983 : /* It's OK if both types are integer types, where one is signed and the
984 : other type the corresponding unsigned type, when the value is
985 : representable in both types. */
986 29 : if (INTEGRAL_TYPE_P (lhs_type)
987 17 : && INTEGRAL_TYPE_P (arg_type)
988 10 : && TYPE_UNSIGNED (lhs_type) != TYPE_UNSIGNED (arg_type)
989 4 : && TYPE_PRECISION (lhs_type) == TYPE_PRECISION (arg_type)
990 4 : && representable_in_integral_type_p (arg_sval, lhs_type)
991 33 : && representable_in_integral_type_p (arg_sval, arg_type))
992 : return true;
993 :
994 : /* It's OK if one type is a pointer to void and the other is a
995 : pointer to a character type.
996 : This is handled by compat_types_p. */
997 :
998 : /* Otherwise the types are not compatible. */
999 : return false;
1000 : }
1001 :
1002 : /* If AP_SVAL is a pointer to a var_arg_region, return that var_arg_region.
1003 : Otherwise return nullptr. */
1004 :
1005 : static const var_arg_region *
1006 1042 : maybe_get_var_arg_region (const svalue *ap_sval)
1007 : {
1008 1042 : if (const region *reg = ap_sval->maybe_get_region ())
1009 566 : return reg->dyn_cast_var_arg_region ();
1010 : return nullptr;
1011 : }
1012 :
1013 : /* Handler for "__builtin_va_arg". */
1014 :
1015 3377 : class kf_va_arg : public internal_known_function
1016 : {
1017 : public:
1018 : void impl_call_pre (const call_details &cd) const final override;
1019 : };
1020 :
1021 : void
1022 1131 : kf_va_arg::impl_call_pre (const call_details &cd) const
1023 : {
1024 1131 : region_model_context *ctxt = cd.get_ctxt ();
1025 1131 : region_model *model = cd.get_model ();
1026 1131 : region_model_manager *mgr = cd.get_manager ();
1027 :
1028 1131 : const svalue *in_ptr = cd.get_arg_svalue (0);
1029 1131 : const region *ap_reg
1030 1131 : = model->deref_rvalue (in_ptr, cd.get_arg_tree (0), ctxt);
1031 :
1032 1131 : const svalue *ap_sval = model->get_store_value (ap_reg, ctxt);
1033 1131 : if (const svalue *cast = ap_sval->maybe_undo_cast ())
1034 1042 : ap_sval = cast;
1035 :
1036 1131 : tree va_list_tree = get_va_list_diag_arg (cd.get_arg_tree (0));
1037 1131 : ap_sval = model->check_for_poison (ap_sval, va_list_tree, ap_reg, ctxt);
1038 :
1039 1131 : cd.set_any_lhs_with_defaults ();
1040 :
1041 1131 : if (const region *impl_reg = ap_sval->maybe_get_region ())
1042 : {
1043 1042 : const svalue *old_impl_sval = model->get_store_value (impl_reg, ctxt);
1044 2084 : if (const var_arg_region *arg_reg
1045 1042 : = maybe_get_var_arg_region (old_impl_sval))
1046 : {
1047 566 : bool saw_problem = false;
1048 :
1049 566 : const frame_region *frame_reg = arg_reg->get_frame_region ();
1050 566 : unsigned next_arg_idx = arg_reg->get_index ();
1051 :
1052 566 : if (frame_reg->get_stack_depth () > 1)
1053 : {
1054 : /* The interprocedural case: the called frame will have been
1055 : populated with any variadic aruguments.
1056 : Attempt to extract arg_reg to cd's return region (which already
1057 : has a conjured_svalue), or warn if there's a problem
1058 : (incompatible types, or if we've run out of args). */
1059 566 : if (const svalue *arg_sval
1060 : = model->get_store ()->get_any_binding
1061 566 : (mgr->get_store_manager (), arg_reg))
1062 : {
1063 540 : tree lhs_type = cd.get_lhs_type ();
1064 540 : tree arg_type = arg_sval->get_type ();
1065 540 : if (va_arg_compatible_types_p (lhs_type, arg_type, *arg_sval))
1066 515 : cd.maybe_set_lhs (arg_sval);
1067 : else
1068 : {
1069 25 : if (ctxt)
1070 50 : ctxt->warn (std::make_unique <va_arg_type_mismatch>
1071 25 : (va_list_tree,
1072 : arg_reg,
1073 : lhs_type,
1074 : arg_type));
1075 25 : saw_problem = true;
1076 : }
1077 : }
1078 : else
1079 : {
1080 26 : if (ctxt)
1081 26 : ctxt->warn
1082 26 : (std::make_unique <va_list_exhausted> (va_list_tree,
1083 : arg_reg));
1084 : saw_problem = true;
1085 : }
1086 : }
1087 : else
1088 : {
1089 : /* This frame is an entry-point to the analysis, so there won't be
1090 : any specific var_arg_regions populated within it.
1091 : We already have a conjured_svalue for the result, so leave
1092 : it untouched. */
1093 0 : gcc_assert (frame_reg->get_stack_depth () == 1);
1094 : }
1095 :
1096 540 : if (saw_problem)
1097 : {
1098 : /* Set impl_reg to UNKNOWN to suppress further warnings. */
1099 51 : const svalue *new_ap_sval
1100 51 : = mgr->get_or_create_unknown_svalue (impl_reg->get_type ());
1101 51 : model->set_value (impl_reg, new_ap_sval, ctxt);
1102 : }
1103 : else
1104 : {
1105 : /* Update impl_reg to advance to the next arg. */
1106 515 : const region *next_var_arg_region
1107 515 : = mgr->get_var_arg_region (frame_reg, next_arg_idx + 1);
1108 515 : const svalue *new_ap_sval
1109 515 : = mgr->get_ptr_svalue (NULL_TREE, next_var_arg_region);
1110 515 : model->set_value (impl_reg, new_ap_sval, ctxt);
1111 : }
1112 : }
1113 : }
1114 1131 : }
1115 :
1116 : /* Handler for "__builtin_va_end". */
1117 :
1118 3377 : class kf_va_end : public known_function
1119 : {
1120 : public:
1121 0 : bool matches_call_types_p (const call_details &) const
1122 : {
1123 0 : return true;
1124 : }
1125 : };
1126 :
1127 : /* Populate KFM with instances of known functions relating to varargs. */
1128 :
1129 : void
1130 3377 : register_varargs_builtins (known_function_manager &kfm)
1131 : {
1132 3377 : kfm.add (BUILT_IN_VA_START, std::make_unique<kf_va_start> ());
1133 3377 : kfm.add (BUILT_IN_VA_COPY, std::make_unique<kf_va_copy> ());
1134 3377 : kfm.add (IFN_VA_ARG, std::make_unique<kf_va_arg> ());
1135 3377 : kfm.add (BUILT_IN_VA_END, std::make_unique<kf_va_end> ());
1136 3377 : }
1137 :
1138 : } // namespace ana
1139 :
1140 : #endif /* #if ENABLE_ANALYZER */
|