]>
Commit | Line | Data |
---|---|---|
2402dc6b | 1 | /* Implementation of <stdarg.h> within analyzer. |
83ffe9cd | 2 | Copyright (C) 2022-2023 Free Software Foundation, Inc. |
2402dc6b DM |
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" | |
6341f14e | 22 | #define INCLUDE_MEMORY |
2402dc6b DM |
23 | #include "system.h" |
24 | #include "coretypes.h" | |
6341f14e | 25 | #include "make-unique.h" |
2402dc6b DM |
26 | #include "tree.h" |
27 | #include "function.h" | |
28 | #include "basic-block.h" | |
29 | #include "gimple.h" | |
30 | #include "diagnostic-path.h" | |
2402dc6b DM |
31 | #include "analyzer/analyzer.h" |
32 | #include "analyzer/analyzer-logging.h" | |
33 | #include "analyzer/sm.h" | |
34 | #include "analyzer/pending-diagnostic.h" | |
2402dc6b DM |
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" | |
2402dc6b | 41 | #include "analyzer/supergraph.h" |
2402dc6b DM |
42 | #include "analyzer/diagnostic-manager.h" |
43 | #include "analyzer/exploded-graph.h" | |
f443024b | 44 | #include "diagnostic-metadata.h" |
861c917a | 45 | #include "analyzer/call-details.h" |
2402dc6b DM |
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 | |
ebe87eda | 138 | is already target-dependent (see the discussion of get_va_copy_arg |
2402dc6b DM |
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 | get_va_list_diag_arg (tree va_list_tree) | |
147 | { | |
148 | if (TREE_CODE (va_list_tree) == ADDR_EXPR) | |
149 | va_list_tree = TREE_OPERAND (va_list_tree, 0); | |
150 | return va_list_tree; | |
151 | } | |
152 | ||
ebe87eda | 153 | /* Get argument ARG_IDX of va_copy. |
2402dc6b DM |
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 | |
ebe87eda DM |
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. */ | |
2402dc6b DM |
166 | |
167 | static const svalue * | |
ebe87eda DM |
168 | get_va_copy_arg (const region_model *model, |
169 | region_model_context *ctxt, | |
170 | const gcall *call, | |
171 | unsigned arg_idx) | |
2402dc6b DM |
172 | { |
173 | tree arg = gimple_call_arg (call, arg_idx); | |
174 | const svalue *arg_sval = model->get_rvalue (arg, ctxt); | |
175 | if (const svalue *cast = arg_sval->maybe_undo_cast ()) | |
176 | arg_sval = cast; | |
687d11fd DM |
177 | if (TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE |
178 | && TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == ARRAY_TYPE) | |
2402dc6b DM |
179 | { |
180 | /* va_list_arg_type_node is a pointer to a va_list; | |
181 | return *ARG_SVAL. */ | |
182 | const region *src_reg = model->deref_rvalue (arg_sval, arg, ctxt); | |
183 | const svalue *src_reg_sval = model->get_store_value (src_reg, ctxt); | |
184 | if (const svalue *cast = src_reg_sval->maybe_undo_cast ()) | |
185 | src_reg_sval = cast; | |
186 | 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 | ||
ff171cb1 | 208 | bool inherited_state_p () const final override { return false; } |
2402dc6b DM |
209 | |
210 | bool on_stmt (sm_context *sm_ctxt, | |
211 | const supernode *node, | |
ff171cb1 | 212 | const gimple *stmt) const final override; |
2402dc6b | 213 | |
ff171cb1 | 214 | bool can_purge_p (state_t s) const final override |
2402dc6b DM |
215 | { |
216 | return s != m_started; | |
217 | } | |
6341f14e | 218 | std::unique_ptr<pending_diagnostic> on_leak (tree var) const final override; |
2402dc6b | 219 | |
7543a6da | 220 | /* State for a va_list that is the result of a va_start or va_copy. */ |
2402dc6b DM |
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 | va_list_state_machine::va_list_state_machine (logger *logger) | |
245 | : state_machine ("va_list", logger) | |
246 | { | |
247 | m_started = add_state ("started"); | |
248 | m_ended = add_state ("ended"); | |
249 | } | |
250 | ||
251 | /* Implementation of the various "va_*" functions for | |
252 | va_list_state_machine. */ | |
253 | ||
254 | bool | |
255 | va_list_state_machine::on_stmt (sm_context *sm_ctxt, | |
256 | const supernode *node, | |
257 | const gimple *stmt) const | |
258 | { | |
259 | if (const gcall *call = dyn_cast <const gcall *> (stmt)) | |
260 | { | |
261 | if (gimple_call_internal_p (call) | |
262 | && gimple_call_internal_fn (call) == IFN_VA_ARG) | |
263 | { | |
264 | on_va_arg (sm_ctxt, node, call); | |
265 | return false; | |
266 | } | |
267 | ||
268 | if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) | |
269 | if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL) | |
270 | && gimple_builtin_call_types_compatible_p (call, callee_fndecl)) | |
271 | switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) | |
272 | { | |
273 | default: | |
274 | break; | |
275 | ||
276 | case BUILT_IN_VA_START: | |
277 | on_va_start (sm_ctxt, node, call); | |
278 | break; | |
279 | ||
280 | case BUILT_IN_VA_COPY: | |
281 | on_va_copy (sm_ctxt, node, call); | |
282 | break; | |
283 | ||
284 | case BUILT_IN_VA_END: | |
285 | on_va_end (sm_ctxt, node, call); | |
286 | 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 | get_stateful_arg (sm_context *sm_ctxt, const gcall *call, unsigned arg_idx) | |
297 | { | |
298 | tree ap = gimple_call_arg (call, arg_idx); | |
299 | if (ap | |
300 | && POINTER_TYPE_P (TREE_TYPE (ap))) | |
301 | { | |
302 | if (const program_state *new_state = sm_ctxt->get_new_program_state ()) | |
303 | { | |
304 | const region_model *new_model = new_state->m_region_model; | |
305 | const svalue *ptr_sval = new_model->get_rvalue (ap, NULL); | |
306 | const region *reg = new_model->deref_rvalue (ptr_sval, ap, NULL); | |
307 | const svalue *impl_sval = new_model->get_store_value (reg, NULL); | |
308 | if (const svalue *cast = impl_sval->maybe_undo_cast ()) | |
309 | impl_sval = cast; | |
310 | return impl_sval; | |
311 | } | |
312 | } | |
313 | return NULL; | |
314 | } | |
315 | ||
316 | /* Abstract class for diagnostics relating to va_list_state_machine. */ | |
317 | ||
318 | class va_list_sm_diagnostic : public pending_diagnostic | |
319 | { | |
320 | public: | |
ff171cb1 | 321 | bool subclass_equal_p (const pending_diagnostic &base_other) const override |
2402dc6b DM |
322 | { |
323 | const va_list_sm_diagnostic &other | |
324 | = (const va_list_sm_diagnostic &)base_other; | |
325 | return (m_ap_sval == other.m_ap_sval | |
326 | && same_tree_p (m_ap_tree, other.m_ap_tree)); | |
327 | } | |
328 | ||
329 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 330 | override |
2402dc6b DM |
331 | { |
332 | if (const char *fnname = maybe_get_fnname (change)) | |
333 | return change.formatted_print ("%qs called here", fnname); | |
334 | return label_text (); | |
335 | } | |
336 | ||
6cf276dd DM |
337 | diagnostic_event::meaning |
338 | get_meaning_for_state_change (const evdesc::state_change &change) | |
339 | const final override | |
340 | { | |
341 | if (change.m_new_state == m_sm.m_started) | |
342 | return diagnostic_event::meaning (diagnostic_event::VERB_acquire, | |
343 | diagnostic_event::NOUN_resource); | |
344 | if (change.m_new_state == m_sm.m_ended) | |
345 | return diagnostic_event::meaning (diagnostic_event::VERB_release, | |
346 | diagnostic_event::NOUN_resource); | |
347 | return diagnostic_event::meaning (); | |
348 | } | |
349 | ||
2402dc6b DM |
350 | protected: |
351 | va_list_sm_diagnostic (const va_list_state_machine &sm, | |
352 | const svalue *ap_sval, tree ap_tree) | |
353 | : m_sm (sm), m_ap_sval (ap_sval), m_ap_tree (ap_tree) | |
354 | {} | |
355 | ||
356 | static const char *maybe_get_fnname (const evdesc::state_change &change) | |
357 | { | |
358 | if (change.m_event.m_stmt) | |
359 | if (const gcall *call = as_a <const gcall *> (change.m_event.m_stmt)) | |
360 | if (tree callee_fndecl = gimple_call_fndecl (call)) | |
361 | { | |
362 | if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)) | |
363 | switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) | |
364 | { | |
365 | case BUILT_IN_VA_START: | |
366 | return "va_start"; | |
367 | case BUILT_IN_VA_COPY: | |
368 | return "va_copy"; | |
369 | case BUILT_IN_VA_END: | |
370 | return "va_end"; | |
371 | } | |
372 | } | |
373 | return NULL; | |
374 | } | |
375 | ||
376 | const va_list_state_machine &m_sm; | |
377 | const svalue *m_ap_sval; | |
378 | tree m_ap_tree; | |
379 | }; | |
380 | ||
381 | /* Concrete class for -Wanalyzer-va-list-use-after-va-end: | |
382 | complain about use of a va_list after va_end has been called on it. */ | |
383 | ||
384 | class va_list_use_after_va_end : public va_list_sm_diagnostic | |
385 | { | |
386 | public: | |
387 | va_list_use_after_va_end (const va_list_state_machine &sm, | |
388 | const svalue *ap_sval, tree ap_tree, | |
389 | const char *usage_fnname) | |
390 | : va_list_sm_diagnostic (sm, ap_sval, ap_tree), | |
391 | m_usage_fnname (usage_fnname) | |
392 | { | |
393 | } | |
394 | ||
ff171cb1 | 395 | int get_controlling_option () const final override |
2402dc6b DM |
396 | { |
397 | return OPT_Wanalyzer_va_list_use_after_va_end; | |
398 | } | |
399 | ||
400 | bool operator== (const va_list_use_after_va_end &other) const | |
401 | { | |
402 | return (va_list_sm_diagnostic::subclass_equal_p (other) | |
403 | && 0 == strcmp (m_usage_fnname, other.m_usage_fnname)); | |
404 | } | |
405 | ||
0e466e97 | 406 | bool emit (rich_location *rich_loc, logger *) final override |
2402dc6b DM |
407 | { |
408 | auto_diagnostic_group d; | |
409 | return warning_at (rich_loc, get_controlling_option (), | |
410 | "%qs after %qs", m_usage_fnname, "va_end"); | |
411 | } | |
412 | ||
ff171cb1 | 413 | const char *get_kind () const final override |
2402dc6b DM |
414 | { |
415 | return "va_list_use_after_va_end"; | |
416 | } | |
417 | ||
418 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 419 | final override |
2402dc6b DM |
420 | { |
421 | if (change.m_new_state == m_sm.m_ended) | |
422 | m_va_end_event = change.m_event_id; | |
423 | return va_list_sm_diagnostic::describe_state_change (change); | |
424 | } | |
425 | ||
ff171cb1 | 426 | label_text describe_final_event (const evdesc::final_event &ev) final override |
2402dc6b DM |
427 | { |
428 | if (ev.m_expr) | |
429 | { | |
430 | if (m_va_end_event.known_p ()) | |
431 | return ev.formatted_print | |
432 | ("%qs on %qE after %qs at %@", | |
433 | m_usage_fnname, ev.m_expr, "va_end", &m_va_end_event); | |
434 | else | |
435 | return ev.formatted_print | |
436 | ("%qs on %qE after %qs", | |
437 | m_usage_fnname, ev.m_expr, "va_end"); | |
438 | } | |
439 | else | |
440 | { | |
441 | if (m_va_end_event.known_p ()) | |
442 | return ev.formatted_print | |
443 | ("%qs after %qs at %@", | |
444 | m_usage_fnname, "va_end", &m_va_end_event); | |
445 | else | |
446 | return ev.formatted_print | |
447 | ("%qs after %qs", | |
448 | m_usage_fnname, "va_end"); | |
449 | } | |
450 | } | |
451 | ||
452 | private: | |
453 | diagnostic_event_id_t m_va_end_event; | |
454 | const char *m_usage_fnname; | |
455 | }; | |
456 | ||
457 | /* Concrete class for -Wanalyzer-va-list-leak: | |
458 | complain about a va_list in the "started" state that doesn't get after | |
459 | va_end called on it. */ | |
460 | ||
461 | class va_list_leak : public va_list_sm_diagnostic | |
462 | { | |
463 | public: | |
464 | va_list_leak (const va_list_state_machine &sm, | |
465 | const svalue *ap_sval, tree ap_tree) | |
466 | : va_list_sm_diagnostic (sm, ap_sval, ap_tree), | |
467 | m_start_event_fnname (NULL) | |
468 | { | |
469 | } | |
470 | ||
ff171cb1 | 471 | int get_controlling_option () const final override |
2402dc6b DM |
472 | { |
473 | return OPT_Wanalyzer_va_list_leak; | |
474 | } | |
475 | ||
476 | bool operator== (const va_list_leak &other) const | |
477 | { | |
478 | return va_list_sm_diagnostic::subclass_equal_p (other); | |
479 | } | |
480 | ||
0e466e97 | 481 | bool emit (rich_location *rich_loc, logger *) final override |
2402dc6b DM |
482 | { |
483 | auto_diagnostic_group d; | |
484 | return warning_at (rich_loc, get_controlling_option (), | |
485 | "missing call to %qs", "va_end"); | |
486 | } | |
487 | ||
ff171cb1 | 488 | const char *get_kind () const final override { return "va_list_leak"; } |
2402dc6b DM |
489 | |
490 | label_text describe_state_change (const evdesc::state_change &change) | |
ff171cb1 | 491 | final override |
2402dc6b DM |
492 | { |
493 | if (change.m_new_state == m_sm.m_started) | |
494 | { | |
495 | m_start_event = change.m_event_id; | |
496 | m_start_event_fnname = maybe_get_fnname (change); | |
497 | } | |
498 | return va_list_sm_diagnostic::describe_state_change (change); | |
499 | } | |
500 | ||
ff171cb1 | 501 | label_text describe_final_event (const evdesc::final_event &ev) final override |
2402dc6b DM |
502 | { |
503 | if (ev.m_expr) | |
504 | { | |
505 | if (m_start_event.known_p () && m_start_event_fnname) | |
506 | return ev.formatted_print | |
507 | ("missing call to %qs on %qE to match %qs at %@", | |
508 | "va_end", ev.m_expr, m_start_event_fnname, &m_start_event); | |
509 | else | |
510 | return ev.formatted_print | |
511 | ("missing call to %qs on %qE", | |
512 | "va_end", ev.m_expr); | |
513 | } | |
514 | else | |
515 | { | |
516 | if (m_start_event.known_p () && m_start_event_fnname) | |
517 | return ev.formatted_print | |
518 | ("missing call to %qs to match %qs at %@", | |
519 | "va_end", m_start_event_fnname, &m_start_event); | |
520 | else | |
521 | return ev.formatted_print | |
522 | ("missing call to %qs", | |
523 | "va_end"); | |
524 | } | |
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 | va_list_state_machine::on_va_start (sm_context *sm_ctxt, | |
536 | const supernode *, | |
537 | const gcall *call) const | |
538 | { | |
539 | const svalue *arg = get_stateful_arg (sm_ctxt, call, 0); | |
540 | if (arg) | |
541 | { | |
542 | /* Transition from start state to "started". */ | |
543 | if (sm_ctxt->get_state (call, arg) == m_start) | |
544 | sm_ctxt->set_next_state (call, arg, m_started); | |
545 | } | |
546 | } | |
547 | ||
548 | /* Complain if ARG is in the "ended" state. */ | |
549 | ||
550 | void | |
551 | 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 | if (sm_ctxt->get_state (call, arg) == m_ended) | |
558 | sm_ctxt->warn (node, call, arg, | |
6341f14e DM |
559 | make_unique<va_list_use_after_va_end> |
560 | (*this, arg, NULL_TREE, usage_fnname)); | |
2402dc6b DM |
561 | } |
562 | ||
ebe87eda DM |
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, | |
2402dc6b DM |
565 | or NULL otherwise. */ |
566 | ||
567 | static const svalue * | |
ebe87eda DM |
568 | get_stateful_va_copy_arg (sm_context *sm_ctxt, |
569 | const gcall *call, | |
570 | unsigned arg_idx) | |
2402dc6b DM |
571 | { |
572 | if (const program_state *new_state = sm_ctxt->get_new_program_state ()) | |
573 | { | |
574 | const region_model *new_model = new_state->m_region_model; | |
ebe87eda | 575 | const svalue *arg = get_va_copy_arg (new_model, NULL, call, arg_idx); |
2402dc6b DM |
576 | return arg; |
577 | } | |
578 | return NULL; | |
579 | } | |
580 | ||
581 | /* Update state machine for a "va_copy" call. */ | |
582 | ||
583 | void | |
584 | va_list_state_machine::on_va_copy (sm_context *sm_ctxt, | |
585 | const supernode *node, | |
586 | const gcall *call) const | |
587 | { | |
ebe87eda | 588 | const svalue *src_arg = get_stateful_va_copy_arg (sm_ctxt, call, 1); |
2402dc6b DM |
589 | if (src_arg) |
590 | check_for_ended_va_list (sm_ctxt, node, call, src_arg, "va_copy"); | |
591 | ||
592 | const svalue *dst_arg = get_stateful_arg (sm_ctxt, call, 0); | |
593 | if (dst_arg) | |
594 | { | |
595 | /* Transition from start state to "started". */ | |
596 | if (sm_ctxt->get_state (call, dst_arg) == m_start) | |
597 | sm_ctxt->set_next_state (call, dst_arg, m_started); | |
598 | } | |
599 | } | |
600 | ||
601 | /* Update state machine for a "va_arg" call. */ | |
602 | ||
603 | void | |
604 | va_list_state_machine::on_va_arg (sm_context *sm_ctxt, | |
605 | const supernode *node, | |
606 | const gcall *call) const | |
607 | { | |
608 | const svalue *arg = get_stateful_arg (sm_ctxt, call, 0); | |
609 | if (arg) | |
610 | check_for_ended_va_list (sm_ctxt, node, call, arg, "va_arg"); | |
611 | } | |
612 | ||
613 | /* Update state machine for a "va_end" call. */ | |
614 | ||
615 | void | |
616 | va_list_state_machine::on_va_end (sm_context *sm_ctxt, | |
617 | const supernode *node, | |
618 | const gcall *call) const | |
619 | { | |
620 | const svalue *arg = get_stateful_arg (sm_ctxt, call, 0); | |
621 | if (arg) | |
622 | { | |
623 | state_t s = sm_ctxt->get_state (call, arg); | |
624 | /* Transition from "started" to "ended". */ | |
625 | if (s == m_started) | |
626 | sm_ctxt->set_next_state (call, arg, m_ended); | |
627 | else if (s == m_ended) | |
628 | check_for_ended_va_list (sm_ctxt, node, call, arg, "va_end"); | |
629 | } | |
630 | } | |
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 | ||
6341f14e | 635 | std::unique_ptr<pending_diagnostic> |
2402dc6b DM |
636 | va_list_state_machine::on_leak (tree var) const |
637 | { | |
6341f14e | 638 | return make_unique<va_list_leak> (*this, NULL, var); |
2402dc6b DM |
639 | } |
640 | ||
641 | } // anonymous namespace | |
642 | ||
643 | /* Internal interface to this file. */ | |
644 | ||
645 | state_machine * | |
646 | make_va_list_state_machine (logger *logger) | |
647 | { | |
648 | return new va_list_state_machine (logger); | |
649 | } | |
650 | ||
6bd31b33 DM |
651 | /* Handler for "__builtin_va_start". */ |
652 | ||
653 | class kf_va_start : public known_function | |
654 | { | |
655 | public: | |
4d6f6fd1 | 656 | bool matches_call_types_p (const call_details &) const final override |
6bd31b33 DM |
657 | { |
658 | return true; | |
659 | } | |
660 | void impl_call_pre (const call_details &cd) const final override; | |
661 | }; | |
2402dc6b DM |
662 | |
663 | void | |
6bd31b33 | 664 | kf_va_start::impl_call_pre (const call_details &cd) const |
2402dc6b | 665 | { |
6bd31b33 DM |
666 | region_model *model = cd.get_model (); |
667 | region_model_manager *mgr = cd.get_manager (); | |
2402dc6b DM |
668 | const svalue *out_ptr = cd.get_arg_svalue (0); |
669 | const region *out_reg | |
6bd31b33 DM |
670 | = model->deref_rvalue (out_ptr, cd.get_arg_tree (0), cd.get_ctxt ()); |
671 | const frame_region *frame = model->get_current_frame (); | |
2402dc6b DM |
672 | |
673 | /* "*out_ptr = &IMPL_REGION;". */ | |
6bd31b33 | 674 | const region *impl_reg = mgr->create_region_for_alloca (frame); |
2402dc6b DM |
675 | |
676 | /* We abuse the types here, since va_list_type isn't | |
677 | necessarily anything to do with a pointer. */ | |
6bd31b33 DM |
678 | const svalue *ptr_to_impl_reg = mgr->get_ptr_svalue (NULL_TREE, impl_reg); |
679 | model->set_value (out_reg, ptr_to_impl_reg, cd.get_ctxt ()); | |
2402dc6b | 680 | |
6bd31b33 | 681 | if (model->get_stack_depth () > 1) |
6d5194a1 DM |
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. */ | |
6bd31b33 | 686 | const region *init_var_arg_reg = mgr->get_var_arg_region (frame, 0); |
6d5194a1 | 687 | const svalue *ap_sval |
6bd31b33 DM |
688 | = mgr->get_ptr_svalue (NULL_TREE, init_var_arg_reg); |
689 | model->set_value (impl_reg, ap_sval, cd.get_ctxt ()); | |
6d5194a1 DM |
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 | const svalue *unknown_sval | |
6bd31b33 DM |
698 | = mgr->get_or_create_unknown_svalue (NULL_TREE); |
699 | model->set_value (impl_reg, unknown_sval, cd.get_ctxt ()); | |
6d5194a1 | 700 | } |
2402dc6b DM |
701 | } |
702 | ||
6bd31b33 DM |
703 | /* Handler for "__builtin_va_copy". */ |
704 | ||
705 | class kf_va_copy : public known_function | |
706 | { | |
707 | public: | |
4d6f6fd1 | 708 | bool matches_call_types_p (const call_details &) const final override |
6bd31b33 DM |
709 | { |
710 | return true; | |
711 | } | |
712 | void impl_call_pre (const call_details &cd) const final override; | |
713 | }; | |
2402dc6b DM |
714 | |
715 | void | |
6bd31b33 | 716 | kf_va_copy::impl_call_pre (const call_details &cd) const |
2402dc6b | 717 | { |
6bd31b33 DM |
718 | region_model *model = cd.get_model (); |
719 | region_model_manager *mgr = cd.get_manager (); | |
2402dc6b DM |
720 | const svalue *out_dst_ptr = cd.get_arg_svalue (0); |
721 | const svalue *in_va_list | |
6bd31b33 DM |
722 | = get_va_copy_arg (model, cd.get_ctxt (), cd.get_call_stmt (), 1); |
723 | in_va_list | |
724 | = model->check_for_poison (in_va_list, | |
725 | get_va_list_diag_arg (cd.get_arg_tree (1)), | |
2fdc8546 | 726 | NULL, |
6bd31b33 | 727 | cd.get_ctxt ()); |
2402dc6b DM |
728 | |
729 | const region *out_dst_reg | |
6bd31b33 | 730 | = model->deref_rvalue (out_dst_ptr, cd.get_arg_tree (0), cd.get_ctxt ()); |
2402dc6b DM |
731 | |
732 | /* "*out_dst_ptr = &NEW_IMPL_REGION;". */ | |
733 | const region *new_impl_reg | |
6bd31b33 | 734 | = mgr->create_region_for_alloca (model->get_current_frame ()); |
2402dc6b | 735 | const svalue *ptr_to_new_impl_reg |
6bd31b33 DM |
736 | = mgr->get_ptr_svalue (NULL_TREE, new_impl_reg); |
737 | model->set_value (out_dst_reg, ptr_to_new_impl_reg, cd.get_ctxt ()); | |
2402dc6b DM |
738 | |
739 | if (const region *old_impl_reg = in_va_list->maybe_get_region ()) | |
740 | { | |
2402dc6b DM |
741 | /* "(NEW_IMPL_REGION) = (OLD_IMPL_REGION);". */ |
742 | const svalue *existing_sval | |
6bd31b33 DM |
743 | = model->get_store_value (old_impl_reg, cd.get_ctxt ()); |
744 | model->set_value (new_impl_reg, existing_sval, cd.get_ctxt ()); | |
2402dc6b DM |
745 | } |
746 | } | |
747 | ||
748 | /* Get the number of variadic arguments to CALLEE_FNDECL at CALL_STMT. */ | |
749 | ||
750 | static int | |
751 | get_num_variadic_arguments (tree callee_fndecl, | |
752 | const gcall *call_stmt) | |
753 | { | |
754 | int num_positional = 0; | |
755 | for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm; | |
756 | iter_parm = DECL_CHAIN (iter_parm)) | |
757 | num_positional++; | |
758 | 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 | void add_call_event (const exploded_edge &eedge, | |
ff171cb1 | 774 | checker_path *emission_path) override |
2402dc6b DM |
775 | { |
776 | /* As per call_event, but show the number of variadic arguments | |
777 | in the call. */ | |
778 | class va_arg_call_event : public call_event | |
779 | { | |
780 | public: | |
781 | va_arg_call_event (const exploded_edge &eedge, | |
e24fe128 | 782 | const event_loc_info &loc_info, |
2402dc6b | 783 | int num_variadic_arguments) |
e24fe128 | 784 | : call_event (eedge, loc_info), |
2402dc6b DM |
785 | m_num_variadic_arguments (num_variadic_arguments) |
786 | { | |
787 | } | |
788 | ||
ff171cb1 | 789 | label_text get_desc (bool can_colorize) const override |
2402dc6b DM |
790 | { |
791 | return make_label_text_n | |
792 | (can_colorize, 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 | m_num_variadic_arguments); | |
798 | } | |
799 | private: | |
800 | int m_num_variadic_arguments; | |
801 | }; | |
802 | ||
803 | const frame_region *frame_reg = m_var_arg_reg->get_frame_region (); | |
804 | const exploded_node *dst_node = eedge.m_dest; | |
805 | if (dst_node->get_state ().m_region_model->get_current_frame () | |
806 | == frame_reg) | |
807 | { | |
808 | const exploded_node *src_node = eedge.m_src; | |
809 | const program_point &src_point = src_node->get_point (); | |
810 | const int src_stack_depth = src_point.get_stack_depth (); | |
811 | const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt (); | |
812 | const gcall *call_stmt = as_a <const gcall *> (last_stmt); | |
813 | int num_variadic_arguments | |
814 | = get_num_variadic_arguments (dst_node->get_function ()->decl, | |
815 | call_stmt); | |
816 | emission_path->add_event | |
e24fe128 DM |
817 | (make_unique<va_arg_call_event> |
818 | (eedge, | |
819 | event_loc_info (last_stmt ? last_stmt->location : UNKNOWN_LOCATION, | |
820 | src_point.get_fndecl (), | |
821 | src_stack_depth), | |
822 | num_variadic_arguments)); | |
2402dc6b DM |
823 | } |
824 | else | |
825 | pending_diagnostic::add_call_event (eedge, emission_path); | |
826 | } | |
827 | ||
828 | protected: | |
829 | va_arg_diagnostic (tree va_list_tree, const var_arg_region *var_arg_reg) | |
830 | : m_va_list_tree (va_list_tree), m_var_arg_reg (var_arg_reg) | |
831 | {} | |
832 | ||
ff171cb1 | 833 | bool subclass_equal_p (const pending_diagnostic &base_other) const override |
2402dc6b DM |
834 | { |
835 | const va_arg_diagnostic &other = (const va_arg_diagnostic &)base_other; | |
836 | return (same_tree_p (m_va_list_tree, other.m_va_list_tree) | |
837 | && 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 | unsigned get_num_consumed () const | |
843 | { | |
844 | return m_var_arg_reg->get_index (); | |
845 | } | |
846 | ||
847 | /* Get a 1-based index of which variadic argument is being consumed. */ | |
848 | unsigned get_variadic_index_for_diagnostic () const | |
849 | { | |
850 | 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 | va_arg_type_mismatch (tree va_list_tree, const var_arg_region *var_arg_reg, | |
869 | tree expected_type, tree actual_type) | |
870 | : va_arg_diagnostic (va_list_tree, var_arg_reg), | |
871 | m_expected_type (expected_type), m_actual_type (actual_type) | |
872 | {} | |
873 | ||
ff171cb1 | 874 | const char *get_kind () const final override |
2402dc6b DM |
875 | { |
876 | return "va_arg_type_mismatch"; | |
877 | } | |
878 | ||
879 | bool subclass_equal_p (const pending_diagnostic &base_other) | |
ff171cb1 | 880 | const final override |
2402dc6b DM |
881 | { |
882 | if (!va_arg_diagnostic::subclass_equal_p (base_other)) | |
883 | return false; | |
884 | const va_arg_type_mismatch &other | |
885 | = (const va_arg_type_mismatch &)base_other; | |
886 | return (same_tree_p (m_expected_type, other.m_expected_type) | |
887 | && same_tree_p (m_actual_type, other.m_actual_type)); | |
888 | } | |
889 | ||
ff171cb1 | 890 | int get_controlling_option () const final override |
2402dc6b DM |
891 | { |
892 | return OPT_Wanalyzer_va_arg_type_mismatch; | |
893 | } | |
894 | ||
0e466e97 | 895 | bool emit (rich_location *rich_loc, logger *) final override |
2402dc6b DM |
896 | { |
897 | auto_diagnostic_group d; | |
f0da5f0a DM |
898 | diagnostic_metadata m; |
899 | /* "CWE-686: Function Call With Incorrect Argument Type". */ | |
900 | m.add_cwe (686); | |
2402dc6b | 901 | bool warned |
f0da5f0a DM |
902 | = warning_meta (rich_loc, m, get_controlling_option (), |
903 | "%<va_arg%> expected %qT but received %qT" | |
904 | " for variadic argument %i of %qE", | |
905 | m_expected_type, m_actual_type, | |
906 | get_variadic_index_for_diagnostic (), m_va_list_tree); | |
2402dc6b DM |
907 | return warned; |
908 | } | |
909 | ||
ff171cb1 | 910 | label_text describe_final_event (const evdesc::final_event &ev) final override |
2402dc6b DM |
911 | { |
912 | return ev.formatted_print ("%<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 | } | |
918 | ||
919 | private: | |
920 | tree m_expected_type; | |
921 | tree m_actual_type; | |
922 | }; | |
923 | ||
924 | /* A subclass of pending_diagnostic for complaining about a | |
925 | va_arg (AP); | |
926 | after all of the args in AP have been consumed. */ | |
927 | ||
928 | class va_list_exhausted : public va_arg_diagnostic | |
929 | { | |
930 | public: | |
931 | va_list_exhausted (tree va_list_tree, const var_arg_region *var_arg_reg) | |
932 | : va_arg_diagnostic (va_list_tree, var_arg_reg) | |
933 | {} | |
934 | ||
ff171cb1 | 935 | const char *get_kind () const final override |
2402dc6b DM |
936 | { |
937 | return "va_list_exhausted"; | |
938 | } | |
939 | ||
ff171cb1 | 940 | int get_controlling_option () const final override |
2402dc6b DM |
941 | { |
942 | return OPT_Wanalyzer_va_list_exhausted; | |
943 | } | |
944 | ||
0e466e97 | 945 | bool emit (rich_location *rich_loc, logger *) final override |
2402dc6b DM |
946 | { |
947 | auto_diagnostic_group d; | |
f443024b DM |
948 | diagnostic_metadata m; |
949 | /* CWE-685: Function Call With Incorrect Number of Arguments. */ | |
950 | m.add_cwe (685); | |
951 | bool warned = warning_meta (rich_loc, m, get_controlling_option (), | |
952 | "%qE has no more arguments (%i consumed)", | |
953 | m_va_list_tree, get_num_consumed ()); | |
2402dc6b DM |
954 | return warned; |
955 | } | |
956 | ||
ff171cb1 | 957 | label_text describe_final_event (const evdesc::final_event &ev) final override |
2402dc6b DM |
958 | { |
959 | return ev.formatted_print ("%qE has no more arguments (%i consumed)", | |
960 | m_va_list_tree, get_num_consumed ()); | |
961 | } | |
962 | }; | |
963 | ||
964 | /* Return true if it's OK to copy a value from ARG_TYPE to LHS_TYPE via | |
965 | va_arg (where argument promotion has already happened). */ | |
966 | ||
967 | static bool | |
968 | va_arg_compatible_types_p (tree lhs_type, tree arg_type) | |
969 | { | |
970 | return compat_types_p (arg_type, lhs_type); | |
971 | } | |
972 | ||
973 | /* If AP_SVAL is a pointer to a var_arg_region, return that var_arg_region. | |
974 | Otherwise return NULL. */ | |
975 | ||
976 | static const var_arg_region * | |
977 | maybe_get_var_arg_region (const svalue *ap_sval) | |
978 | { | |
979 | if (const region *reg = ap_sval->maybe_get_region ()) | |
980 | return reg->dyn_cast_var_arg_region (); | |
981 | return NULL; | |
982 | } | |
983 | ||
6bd31b33 DM |
984 | /* Handler for "__builtin_va_arg". */ |
985 | ||
986 | class kf_va_arg : public internal_known_function | |
987 | { | |
988 | public: | |
989 | void impl_call_pre (const call_details &cd) const final override; | |
990 | }; | |
2402dc6b DM |
991 | |
992 | void | |
6bd31b33 | 993 | kf_va_arg::impl_call_pre (const call_details &cd) const |
2402dc6b DM |
994 | { |
995 | region_model_context *ctxt = cd.get_ctxt (); | |
6bd31b33 DM |
996 | region_model *model = cd.get_model (); |
997 | region_model_manager *mgr = cd.get_manager (); | |
2402dc6b DM |
998 | |
999 | const svalue *in_ptr = cd.get_arg_svalue (0); | |
6bd31b33 DM |
1000 | const region *ap_reg |
1001 | = model->deref_rvalue (in_ptr, cd.get_arg_tree (0), ctxt); | |
2402dc6b | 1002 | |
6bd31b33 | 1003 | const svalue *ap_sval = model->get_store_value (ap_reg, ctxt); |
2402dc6b DM |
1004 | if (const svalue *cast = ap_sval->maybe_undo_cast ()) |
1005 | ap_sval = cast; | |
1006 | ||
1007 | tree va_list_tree = get_va_list_diag_arg (cd.get_arg_tree (0)); | |
2fdc8546 | 1008 | ap_sval = model->check_for_poison (ap_sval, va_list_tree, ap_reg, ctxt); |
2402dc6b | 1009 | |
73da34a5 DM |
1010 | cd.set_any_lhs_with_defaults (); |
1011 | ||
2402dc6b DM |
1012 | if (const region *impl_reg = ap_sval->maybe_get_region ()) |
1013 | { | |
6bd31b33 | 1014 | const svalue *old_impl_sval = model->get_store_value (impl_reg, ctxt); |
2402dc6b DM |
1015 | if (const var_arg_region *arg_reg |
1016 | = maybe_get_var_arg_region (old_impl_sval)) | |
1017 | { | |
1018 | bool saw_problem = false; | |
1019 | ||
1020 | const frame_region *frame_reg = arg_reg->get_frame_region (); | |
1021 | unsigned next_arg_idx = arg_reg->get_index (); | |
1022 | ||
b852aa7f | 1023 | if (frame_reg->get_stack_depth () > 1) |
2402dc6b DM |
1024 | { |
1025 | /* The interprocedural case: the called frame will have been | |
1026 | populated with any variadic aruguments. | |
1027 | Attempt to extract arg_reg to cd's return region (which already | |
1028 | has a conjured_svalue), or warn if there's a problem | |
1029 | (incompatible types, or if we've run out of args). */ | |
1030 | if (const svalue *arg_sval | |
6bd31b33 DM |
1031 | = model->get_store ()->get_any_binding |
1032 | (mgr->get_store_manager (), arg_reg)) | |
2402dc6b DM |
1033 | { |
1034 | tree lhs_type = cd.get_lhs_type (); | |
1035 | tree arg_type = arg_sval->get_type (); | |
1036 | if (va_arg_compatible_types_p (lhs_type, arg_type)) | |
1037 | cd.maybe_set_lhs (arg_sval); | |
1038 | else | |
1039 | { | |
1040 | if (ctxt) | |
6341f14e DM |
1041 | ctxt->warn (make_unique <va_arg_type_mismatch> |
1042 | (va_list_tree, | |
1043 | arg_reg, | |
1044 | lhs_type, | |
1045 | arg_type)); | |
2402dc6b DM |
1046 | saw_problem = true; |
1047 | } | |
1048 | } | |
1049 | else | |
1050 | { | |
1051 | if (ctxt) | |
6341f14e DM |
1052 | ctxt->warn (make_unique <va_list_exhausted> (va_list_tree, |
1053 | arg_reg)); | |
2402dc6b DM |
1054 | saw_problem = true; |
1055 | } | |
1056 | } | |
1057 | else | |
1058 | { | |
1059 | /* This frame is an entry-point to the analysis, so there won't be | |
1060 | any specific var_arg_regions populated within it. | |
1061 | We already have a conjured_svalue for the result, so leave | |
1062 | it untouched. */ | |
b852aa7f | 1063 | gcc_assert (frame_reg->get_stack_depth () == 1); |
2402dc6b DM |
1064 | } |
1065 | ||
1066 | if (saw_problem) | |
1067 | { | |
1068 | /* Set impl_reg to UNKNOWN to suppress further warnings. */ | |
1069 | const svalue *new_ap_sval | |
6bd31b33 DM |
1070 | = mgr->get_or_create_unknown_svalue (impl_reg->get_type ()); |
1071 | model->set_value (impl_reg, new_ap_sval, ctxt); | |
2402dc6b DM |
1072 | } |
1073 | else | |
1074 | { | |
1075 | /* Update impl_reg to advance to the next arg. */ | |
1076 | const region *next_var_arg_region | |
6bd31b33 | 1077 | = mgr->get_var_arg_region (frame_reg, next_arg_idx + 1); |
2402dc6b | 1078 | const svalue *new_ap_sval |
6bd31b33 DM |
1079 | = mgr->get_ptr_svalue (NULL_TREE, next_var_arg_region); |
1080 | model->set_value (impl_reg, new_ap_sval, ctxt); | |
2402dc6b DM |
1081 | } |
1082 | } | |
1083 | } | |
1084 | } | |
1085 | ||
6bd31b33 DM |
1086 | /* Handler for "__builtin_va_end". */ |
1087 | ||
1088 | class kf_va_end : public known_function | |
1089 | { | |
1090 | public: | |
1091 | bool matches_call_types_p (const call_details &) const | |
1092 | { | |
1093 | return true; | |
1094 | } | |
1095 | }; | |
1096 | ||
1097 | /* Populate KFM with instances of known functions relating to varargs. */ | |
2402dc6b DM |
1098 | |
1099 | void | |
6bd31b33 | 1100 | register_varargs_builtins (known_function_manager &kfm) |
2402dc6b | 1101 | { |
6bd31b33 DM |
1102 | kfm.add (BUILT_IN_VA_START, make_unique<kf_va_start> ()); |
1103 | kfm.add (BUILT_IN_VA_COPY, make_unique<kf_va_copy> ()); | |
1104 | kfm.add (IFN_VA_ARG, make_unique<kf_va_arg> ()); | |
1105 | kfm.add (BUILT_IN_VA_END, make_unique<kf_va_end> ()); | |
2402dc6b DM |
1106 | } |
1107 | ||
1108 | } // namespace ana | |
1109 | ||
1110 | #endif /* #if ENABLE_ANALYZER */ |