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