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