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