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 | get_va_list_diag_arg (tree va_list_tree) |
146 | { |
147 | if (TREE_CODE (va_list_tree) == ADDR_EXPR) |
148 | va_list_tree = TREE_OPERAND (va_list_tree, 0); |
149 | 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 | get_va_copy_arg (const region_model *model, |
168 | region_model_context *ctxt, |
169 | const gcall *call, |
170 | unsigned arg_idx) |
171 | { |
172 | tree arg = gimple_call_arg (gs: call, index: arg_idx); |
173 | const svalue *arg_sval = model->get_rvalue (expr: arg, ctxt); |
174 | if (const svalue *cast = arg_sval->maybe_undo_cast ()) |
175 | arg_sval = cast; |
176 | if (TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE |
177 | && 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 | const region *src_reg = model->deref_rvalue (ptr_sval: arg_sval, ptr_tree: arg, ctxt); |
182 | const svalue *src_reg_sval = model->get_store_value (reg: src_reg, ctxt); |
183 | if (const svalue *cast = src_reg_sval->maybe_undo_cast ()) |
184 | src_reg_sval = cast; |
185 | 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 | 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 | bool can_purge_p (state_t s) const final override |
214 | { |
215 | 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 | va_list_state_machine::va_list_state_machine (logger *logger) |
244 | : state_machine ("va_list" , logger), |
245 | m_started (add_state (name: "started" )), |
246 | m_ended (add_state (name: "ended" )) |
247 | { |
248 | } |
249 | |
250 | /* Implementation of the various "va_*" functions for |
251 | va_list_state_machine. */ |
252 | |
253 | bool |
254 | va_list_state_machine::on_stmt (sm_context *sm_ctxt, |
255 | const supernode *node, |
256 | const gimple *stmt) const |
257 | { |
258 | if (const gcall *call = dyn_cast <const gcall *> (p: stmt)) |
259 | { |
260 | if (gimple_call_internal_p (gs: call) |
261 | && gimple_call_internal_fn (gs: call) == IFN_VA_ARG) |
262 | { |
263 | on_va_arg (sm_ctxt, node, call); |
264 | return false; |
265 | } |
266 | |
267 | if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) |
268 | if (fndecl_built_in_p (node: callee_fndecl, klass: BUILT_IN_NORMAL) |
269 | && gimple_builtin_call_types_compatible_p (call, callee_fndecl)) |
270 | switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) |
271 | { |
272 | default: |
273 | break; |
274 | |
275 | case BUILT_IN_VA_START: |
276 | on_va_start (sm_ctxt, node, call); |
277 | break; |
278 | |
279 | case BUILT_IN_VA_COPY: |
280 | on_va_copy (sm_ctxt, node, call); |
281 | break; |
282 | |
283 | case BUILT_IN_VA_END: |
284 | on_va_end (sm_ctxt, node, call); |
285 | 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 | get_stateful_arg (sm_context *sm_ctxt, const gcall *call, unsigned arg_idx) |
296 | { |
297 | tree ap = gimple_call_arg (gs: call, index: arg_idx); |
298 | if (ap |
299 | && POINTER_TYPE_P (TREE_TYPE (ap))) |
300 | { |
301 | if (const program_state *new_state = sm_ctxt->get_new_program_state ()) |
302 | { |
303 | const region_model *new_model = new_state->m_region_model; |
304 | const svalue *ptr_sval = new_model->get_rvalue (expr: ap, NULL); |
305 | const region *reg = new_model->deref_rvalue (ptr_sval, ptr_tree: ap, NULL); |
306 | const svalue *impl_sval = new_model->get_store_value (reg, NULL); |
307 | if (const svalue *cast = impl_sval->maybe_undo_cast ()) |
308 | impl_sval = cast; |
309 | return impl_sval; |
310 | } |
311 | } |
312 | return NULL; |
313 | } |
314 | |
315 | /* Abstract class for diagnostics relating to va_list_state_machine. */ |
316 | |
317 | class va_list_sm_diagnostic : public pending_diagnostic |
318 | { |
319 | public: |
320 | bool subclass_equal_p (const pending_diagnostic &base_other) const override |
321 | { |
322 | const va_list_sm_diagnostic &other |
323 | = (const va_list_sm_diagnostic &)base_other; |
324 | return (m_ap_sval == other.m_ap_sval |
325 | && same_tree_p (t1: m_ap_tree, t2: other.m_ap_tree)); |
326 | } |
327 | |
328 | label_text describe_state_change (const evdesc::state_change &change) |
329 | override |
330 | { |
331 | if (const char *fnname = maybe_get_fnname (change)) |
332 | return change.formatted_print (fmt: "%qs called here" , fnname); |
333 | return label_text (); |
334 | } |
335 | |
336 | diagnostic_event::meaning |
337 | get_meaning_for_state_change (const evdesc::state_change &change) |
338 | const final override |
339 | { |
340 | if (change.m_new_state == m_sm.m_started) |
341 | return diagnostic_event::meaning (diagnostic_event::VERB_acquire, |
342 | diagnostic_event::NOUN_resource); |
343 | if (change.m_new_state == m_sm.m_ended) |
344 | return diagnostic_event::meaning (diagnostic_event::VERB_release, |
345 | diagnostic_event::NOUN_resource); |
346 | return diagnostic_event::meaning (); |
347 | } |
348 | |
349 | protected: |
350 | va_list_sm_diagnostic (const va_list_state_machine &sm, |
351 | const svalue *ap_sval, tree ap_tree) |
352 | : m_sm (sm), m_ap_sval (ap_sval), m_ap_tree (ap_tree) |
353 | {} |
354 | |
355 | static const char *maybe_get_fnname (const evdesc::state_change &change) |
356 | { |
357 | if (change.m_event.m_stmt) |
358 | if (const gcall *call = as_a <const gcall *> (p: change.m_event.m_stmt)) |
359 | if (tree callee_fndecl = gimple_call_fndecl (gs: call)) |
360 | { |
361 | if (fndecl_built_in_p (node: callee_fndecl, klass: BUILT_IN_NORMAL)) |
362 | switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) |
363 | { |
364 | case BUILT_IN_VA_START: |
365 | return "va_start" ; |
366 | case BUILT_IN_VA_COPY: |
367 | return "va_copy" ; |
368 | case BUILT_IN_VA_END: |
369 | 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 | class va_list_use_after_va_end : public va_list_sm_diagnostic |
384 | { |
385 | public: |
386 | 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 | : va_list_sm_diagnostic (sm, ap_sval, ap_tree), |
390 | m_usage_fnname (usage_fnname) |
391 | { |
392 | } |
393 | |
394 | int get_controlling_option () const final override |
395 | { |
396 | 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 (base_other: other) |
402 | && 0 == strcmp (s1: m_usage_fnname, s2: other.m_usage_fnname)); |
403 | } |
404 | |
405 | bool emit (diagnostic_emission_context &ctxt) final override |
406 | { |
407 | return ctxt.warn ("%qs after %qs" , m_usage_fnname, "va_end" ); |
408 | } |
409 | |
410 | const char *get_kind () const final override |
411 | { |
412 | return "va_list_use_after_va_end" ; |
413 | } |
414 | |
415 | label_text describe_state_change (const evdesc::state_change &change) |
416 | final override |
417 | { |
418 | if (change.m_new_state == m_sm.m_ended) |
419 | m_va_end_event = change.m_event_id; |
420 | return va_list_sm_diagnostic::describe_state_change (change); |
421 | } |
422 | |
423 | label_text describe_final_event (const evdesc::final_event &ev) final override |
424 | { |
425 | if (ev.m_expr) |
426 | { |
427 | if (m_va_end_event.known_p ()) |
428 | return ev.formatted_print |
429 | (fmt: "%qs on %qE after %qs at %@" , |
430 | m_usage_fnname, ev.m_expr, "va_end" , &m_va_end_event); |
431 | else |
432 | return ev.formatted_print |
433 | (fmt: "%qs on %qE after %qs" , |
434 | m_usage_fnname, ev.m_expr, "va_end" ); |
435 | } |
436 | else |
437 | { |
438 | if (m_va_end_event.known_p ()) |
439 | return ev.formatted_print |
440 | (fmt: "%qs after %qs at %@" , |
441 | m_usage_fnname, "va_end" , &m_va_end_event); |
442 | else |
443 | return ev.formatted_print |
444 | (fmt: "%qs after %qs" , |
445 | 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 | class va_list_leak : public va_list_sm_diagnostic |
459 | { |
460 | public: |
461 | va_list_leak (const va_list_state_machine &sm, |
462 | const svalue *ap_sval, tree ap_tree) |
463 | : va_list_sm_diagnostic (sm, ap_sval, ap_tree), |
464 | m_start_event_fnname (NULL) |
465 | { |
466 | } |
467 | |
468 | int get_controlling_option () const final override |
469 | { |
470 | 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 (base_other: other); |
476 | } |
477 | |
478 | bool emit (diagnostic_emission_context &ctxt) final override |
479 | { |
480 | return ctxt.warn ("missing call to %qs" , "va_end" ); |
481 | } |
482 | |
483 | const char *get_kind () const final override { return "va_list_leak" ; } |
484 | |
485 | label_text describe_state_change (const evdesc::state_change &change) |
486 | final override |
487 | { |
488 | if (change.m_new_state == m_sm.m_started) |
489 | { |
490 | m_start_event = change.m_event_id; |
491 | m_start_event_fnname = maybe_get_fnname (change); |
492 | } |
493 | return va_list_sm_diagnostic::describe_state_change (change); |
494 | } |
495 | |
496 | label_text describe_final_event (const evdesc::final_event &ev) final override |
497 | { |
498 | if (ev.m_expr) |
499 | { |
500 | if (m_start_event.known_p () && m_start_event_fnname) |
501 | return ev.formatted_print |
502 | (fmt: "missing call to %qs on %qE to match %qs at %@" , |
503 | "va_end" , ev.m_expr, m_start_event_fnname, &m_start_event); |
504 | else |
505 | return ev.formatted_print |
506 | (fmt: "missing call to %qs on %qE" , |
507 | "va_end" , ev.m_expr); |
508 | } |
509 | else |
510 | { |
511 | if (m_start_event.known_p () && m_start_event_fnname) |
512 | return ev.formatted_print |
513 | (fmt: "missing call to %qs to match %qs at %@" , |
514 | "va_end" , m_start_event_fnname, &m_start_event); |
515 | else |
516 | return ev.formatted_print |
517 | (fmt: "missing call to %qs" , |
518 | "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 | va_list_state_machine::on_va_start (sm_context *sm_ctxt, |
531 | const supernode *, |
532 | const gcall *call) const |
533 | { |
534 | const svalue *arg = get_stateful_arg (sm_ctxt, call, arg_idx: 0); |
535 | if (arg) |
536 | { |
537 | /* Transition from start state to "started". */ |
538 | if (sm_ctxt->get_state (stmt: call, arg) == m_start) |
539 | sm_ctxt->set_next_state (stmt: call, var: arg, to: m_started); |
540 | } |
541 | } |
542 | |
543 | /* Complain if ARG is in the "ended" state. */ |
544 | |
545 | void |
546 | 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 | if (sm_ctxt->get_state (stmt: call, arg) == m_ended) |
553 | sm_ctxt->warn (node, stmt: call, var: arg, |
554 | d: make_unique<va_list_use_after_va_end> |
555 | (args: *this, args&: arg, NULL_TREE, args&: usage_fnname)); |
556 | } |
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 | get_stateful_va_copy_arg (sm_context *sm_ctxt, |
564 | const gcall *call, |
565 | unsigned arg_idx) |
566 | { |
567 | if (const program_state *new_state = sm_ctxt->get_new_program_state ()) |
568 | { |
569 | const region_model *new_model = new_state->m_region_model; |
570 | const svalue *arg = get_va_copy_arg (model: new_model, NULL, call, arg_idx); |
571 | return arg; |
572 | } |
573 | return NULL; |
574 | } |
575 | |
576 | /* Update state machine for a "va_copy" call. */ |
577 | |
578 | void |
579 | va_list_state_machine::on_va_copy (sm_context *sm_ctxt, |
580 | const supernode *node, |
581 | const gcall *call) const |
582 | { |
583 | const svalue *src_arg = get_stateful_va_copy_arg (sm_ctxt, call, arg_idx: 1); |
584 | if (src_arg) |
585 | check_for_ended_va_list (sm_ctxt, node, call, arg: src_arg, usage_fnname: "va_copy" ); |
586 | |
587 | const svalue *dst_arg = get_stateful_arg (sm_ctxt, call, arg_idx: 0); |
588 | if (dst_arg) |
589 | { |
590 | /* Transition from start state to "started". */ |
591 | if (sm_ctxt->get_state (stmt: call, dst_arg) == m_start) |
592 | sm_ctxt->set_next_state (stmt: call, var: dst_arg, to: m_started); |
593 | } |
594 | } |
595 | |
596 | /* Update state machine for a "va_arg" call. */ |
597 | |
598 | void |
599 | va_list_state_machine::on_va_arg (sm_context *sm_ctxt, |
600 | const supernode *node, |
601 | const gcall *call) const |
602 | { |
603 | const svalue *arg = get_stateful_arg (sm_ctxt, call, arg_idx: 0); |
604 | if (arg) |
605 | check_for_ended_va_list (sm_ctxt, node, call, arg, usage_fnname: "va_arg" ); |
606 | } |
607 | |
608 | /* Update state machine for a "va_end" call. */ |
609 | |
610 | void |
611 | va_list_state_machine::on_va_end (sm_context *sm_ctxt, |
612 | const supernode *node, |
613 | const gcall *call) const |
614 | { |
615 | const svalue *arg = get_stateful_arg (sm_ctxt, call, arg_idx: 0); |
616 | if (arg) |
617 | { |
618 | state_t s = sm_ctxt->get_state (stmt: call, arg); |
619 | /* Transition from "started" to "ended". */ |
620 | if (s == m_started) |
621 | sm_ctxt->set_next_state (stmt: call, var: arg, to: m_ended); |
622 | else if (s == m_ended) |
623 | check_for_ended_va_list (sm_ctxt, node, call, arg, usage_fnname: "va_end" ); |
624 | } |
625 | } |
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 | va_list_state_machine::on_leak (tree var) const |
632 | { |
633 | return make_unique<va_list_leak> (args: *this, NULL, args&: var); |
634 | } |
635 | |
636 | } // anonymous namespace |
637 | |
638 | /* Internal interface to this file. */ |
639 | |
640 | state_machine * |
641 | make_va_list_state_machine (logger *logger) |
642 | { |
643 | return new va_list_state_machine (logger); |
644 | } |
645 | |
646 | /* Handler for "__builtin_va_start". */ |
647 | |
648 | class kf_va_start : public known_function |
649 | { |
650 | public: |
651 | bool matches_call_types_p (const call_details &) const final override |
652 | { |
653 | return true; |
654 | } |
655 | void impl_call_pre (const call_details &cd) const final override; |
656 | }; |
657 | |
658 | void |
659 | kf_va_start::impl_call_pre (const call_details &cd) const |
660 | { |
661 | region_model *model = cd.get_model (); |
662 | region_model_manager *mgr = cd.get_manager (); |
663 | const svalue *out_ptr = cd.get_arg_svalue (idx: 0); |
664 | const region *out_reg |
665 | = model->deref_rvalue (ptr_sval: out_ptr, ptr_tree: cd.get_arg_tree (idx: 0), ctxt: cd.get_ctxt ()); |
666 | const frame_region *frame = model->get_current_frame (); |
667 | |
668 | /* "*out_ptr = &IMPL_REGION;". */ |
669 | 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 | const svalue *ptr_to_impl_reg = mgr->get_ptr_svalue (NULL_TREE, pointee: impl_reg); |
674 | model->set_value (lhs_reg: out_reg, rhs_sval: ptr_to_impl_reg, ctxt: cd.get_ctxt ()); |
675 | |
676 | 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 | const region *init_var_arg_reg = mgr->get_var_arg_region (parent: frame, idx: 0); |
682 | const svalue *ap_sval |
683 | = mgr->get_ptr_svalue (NULL_TREE, pointee: init_var_arg_reg); |
684 | model->set_value (lhs_reg: impl_reg, rhs_sval: ap_sval, ctxt: 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 | const svalue *unknown_sval |
693 | = mgr->get_or_create_unknown_svalue (NULL_TREE); |
694 | model->set_value (lhs_reg: impl_reg, rhs_sval: unknown_sval, ctxt: cd.get_ctxt ()); |
695 | } |
696 | } |
697 | |
698 | /* Handler for "__builtin_va_copy". */ |
699 | |
700 | class kf_va_copy : public known_function |
701 | { |
702 | public: |
703 | bool matches_call_types_p (const call_details &) const final override |
704 | { |
705 | return true; |
706 | } |
707 | void impl_call_pre (const call_details &cd) const final override; |
708 | }; |
709 | |
710 | void |
711 | kf_va_copy::impl_call_pre (const call_details &cd) const |
712 | { |
713 | region_model *model = cd.get_model (); |
714 | region_model_manager *mgr = cd.get_manager (); |
715 | const svalue *out_dst_ptr = cd.get_arg_svalue (idx: 0); |
716 | const svalue *in_va_list |
717 | = get_va_copy_arg (model, ctxt: cd.get_ctxt (), call: cd.get_call_stmt (), arg_idx: 1); |
718 | in_va_list |
719 | = model->check_for_poison (sval: in_va_list, |
720 | expr: get_va_list_diag_arg (va_list_tree: cd.get_arg_tree (idx: 1)), |
721 | NULL, |
722 | ctxt: cd.get_ctxt ()); |
723 | |
724 | const region *out_dst_reg |
725 | = model->deref_rvalue (ptr_sval: out_dst_ptr, ptr_tree: cd.get_arg_tree (idx: 0), ctxt: cd.get_ctxt ()); |
726 | |
727 | /* "*out_dst_ptr = &NEW_IMPL_REGION;". */ |
728 | const region *new_impl_reg |
729 | = mgr->create_region_for_alloca (frame: model->get_current_frame ()); |
730 | const svalue *ptr_to_new_impl_reg |
731 | = mgr->get_ptr_svalue (NULL_TREE, pointee: new_impl_reg); |
732 | model->set_value (lhs_reg: out_dst_reg, rhs_sval: ptr_to_new_impl_reg, ctxt: cd.get_ctxt ()); |
733 | |
734 | if (const region *old_impl_reg = in_va_list->maybe_get_region ()) |
735 | { |
736 | /* "(NEW_IMPL_REGION) = (OLD_IMPL_REGION);". */ |
737 | const svalue *existing_sval |
738 | = model->get_store_value (reg: old_impl_reg, ctxt: cd.get_ctxt ()); |
739 | model->set_value (lhs_reg: new_impl_reg, rhs_sval: existing_sval, ctxt: cd.get_ctxt ()); |
740 | } |
741 | } |
742 | |
743 | /* Get the number of variadic arguments to CALLEE_FNDECL at CALL_STMT. */ |
744 | |
745 | static int |
746 | get_num_variadic_arguments (tree callee_fndecl, |
747 | const gcall *call_stmt) |
748 | { |
749 | int num_positional = 0; |
750 | for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm; |
751 | iter_parm = DECL_CHAIN (iter_parm)) |
752 | num_positional++; |
753 | return gimple_call_num_args (gs: 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 | 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 | class va_arg_call_event : public call_event |
774 | { |
775 | public: |
776 | va_arg_call_event (const exploded_edge &eedge, |
777 | const event_loc_info &loc_info, |
778 | int num_variadic_arguments) |
779 | : call_event (eedge, loc_info), |
780 | m_num_variadic_arguments (num_variadic_arguments) |
781 | { |
782 | } |
783 | |
784 | label_text get_desc (bool can_colorize) const override |
785 | { |
786 | return make_label_text_n |
787 | (can_colorize, n: m_num_variadic_arguments, |
788 | singular_fmt: "calling %qE from %qE with %i variadic argument" , |
789 | plural_fmt: "calling %qE from %qE with %i variadic arguments" , |
790 | get_callee_fndecl (), |
791 | get_caller_fndecl (), |
792 | m_num_variadic_arguments); |
793 | } |
794 | private: |
795 | int m_num_variadic_arguments; |
796 | }; |
797 | |
798 | const frame_region *frame_reg = m_var_arg_reg->get_frame_region (); |
799 | const exploded_node *dst_node = eedge.m_dest; |
800 | if (dst_node->get_state ().m_region_model->get_current_frame () |
801 | == frame_reg) |
802 | { |
803 | const exploded_node *src_node = eedge.m_src; |
804 | const program_point &src_point = src_node->get_point (); |
805 | const int src_stack_depth = src_point.get_stack_depth (); |
806 | const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt (); |
807 | const gcall *call_stmt = as_a <const gcall *> (p: last_stmt); |
808 | int num_variadic_arguments |
809 | = get_num_variadic_arguments (callee_fndecl: dst_node->get_function ()->decl, |
810 | call_stmt); |
811 | emission_path->add_event |
812 | (event: make_unique<va_arg_call_event> |
813 | (args: eedge, |
814 | args: event_loc_info (last_stmt ? last_stmt->location : UNKNOWN_LOCATION, |
815 | src_point.get_fndecl (), |
816 | src_stack_depth), |
817 | args&: num_variadic_arguments)); |
818 | } |
819 | else |
820 | pending_diagnostic::add_call_event (eedge, emission_path); |
821 | } |
822 | |
823 | protected: |
824 | va_arg_diagnostic (tree va_list_tree, const var_arg_region *var_arg_reg) |
825 | : m_va_list_tree (va_list_tree), m_var_arg_reg (var_arg_reg) |
826 | {} |
827 | |
828 | bool subclass_equal_p (const pending_diagnostic &base_other) const override |
829 | { |
830 | const va_arg_diagnostic &other = (const va_arg_diagnostic &)base_other; |
831 | return (same_tree_p (t1: m_va_list_tree, t2: other.m_va_list_tree) |
832 | && 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 | unsigned get_num_consumed () const |
838 | { |
839 | return m_var_arg_reg->get_index (); |
840 | } |
841 | |
842 | /* Get a 1-based index of which variadic argument is being consumed. */ |
843 | unsigned get_variadic_index_for_diagnostic () const |
844 | { |
845 | 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 | va_arg_type_mismatch (tree va_list_tree, const var_arg_region *var_arg_reg, |
864 | tree expected_type, tree actual_type) |
865 | : va_arg_diagnostic (va_list_tree, var_arg_reg), |
866 | m_expected_type (expected_type), m_actual_type (actual_type) |
867 | {} |
868 | |
869 | const char *get_kind () const final override |
870 | { |
871 | return "va_arg_type_mismatch" ; |
872 | } |
873 | |
874 | bool subclass_equal_p (const pending_diagnostic &base_other) |
875 | const final override |
876 | { |
877 | if (!va_arg_diagnostic::subclass_equal_p (base_other)) |
878 | return false; |
879 | const va_arg_type_mismatch &other |
880 | = (const va_arg_type_mismatch &)base_other; |
881 | return (same_tree_p (t1: m_expected_type, t2: other.m_expected_type) |
882 | && same_tree_p (t1: m_actual_type, t2: other.m_actual_type)); |
883 | } |
884 | |
885 | int get_controlling_option () const final override |
886 | { |
887 | return OPT_Wanalyzer_va_arg_type_mismatch; |
888 | } |
889 | |
890 | bool emit (diagnostic_emission_context &ctxt) final override |
891 | { |
892 | /* "CWE-686: Function Call With Incorrect Argument Type". */ |
893 | ctxt.add_cwe (cwe: 686); |
894 | bool warned |
895 | = 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 | return warned; |
900 | } |
901 | |
902 | label_text describe_final_event (const evdesc::final_event &ev) final override |
903 | { |
904 | return ev.formatted_print (fmt: "%<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 | 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 | va_list_exhausted (tree va_list_tree, const var_arg_region *var_arg_reg) |
924 | : va_arg_diagnostic (va_list_tree, var_arg_reg) |
925 | {} |
926 | |
927 | const char *get_kind () const final override |
928 | { |
929 | return "va_list_exhausted" ; |
930 | } |
931 | |
932 | int get_controlling_option () const final override |
933 | { |
934 | return OPT_Wanalyzer_va_list_exhausted; |
935 | } |
936 | |
937 | bool emit (diagnostic_emission_context &ctxt) final override |
938 | { |
939 | /* CWE-685: Function Call With Incorrect Number of Arguments. */ |
940 | ctxt.add_cwe (cwe: 685); |
941 | bool warned = ctxt.warn ("%qE has no more arguments (%i consumed)" , |
942 | m_va_list_tree, get_num_consumed ()); |
943 | return warned; |
944 | } |
945 | |
946 | label_text describe_final_event (const evdesc::final_event &ev) final override |
947 | { |
948 | return ev.formatted_print (fmt: "%qE has no more arguments (%i consumed)" , |
949 | m_va_list_tree, get_num_consumed ()); |
950 | } |
951 | }; |
952 | |
953 | static bool |
954 | representable_in_integral_type_p (const svalue &sval, const_tree type) |
955 | { |
956 | gcc_assert (INTEGRAL_TYPE_P (type)); |
957 | |
958 | if (tree cst = sval.maybe_get_constant ()) |
959 | return wi::fits_to_tree_p (x: wi::to_wide (t: 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 | va_arg_compatible_types_p (tree lhs_type, tree arg_type, const svalue &arg_sval) |
969 | { |
970 | if (compat_types_p (src_type: arg_type, dst_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 | if (INTEGRAL_TYPE_P (lhs_type) |
977 | && INTEGRAL_TYPE_P (arg_type) |
978 | && TYPE_UNSIGNED (lhs_type) != TYPE_UNSIGNED (arg_type) |
979 | && TYPE_PRECISION (lhs_type) == TYPE_PRECISION (arg_type) |
980 | && representable_in_integral_type_p (sval: arg_sval, type: lhs_type) |
981 | && representable_in_integral_type_p (sval: arg_sval, type: 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 | maybe_get_var_arg_region (const svalue *ap_sval) |
997 | { |
998 | if (const region *reg = ap_sval->maybe_get_region ()) |
999 | return reg->dyn_cast_var_arg_region (); |
1000 | return NULL; |
1001 | } |
1002 | |
1003 | /* Handler for "__builtin_va_arg". */ |
1004 | |
1005 | 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 | kf_va_arg::impl_call_pre (const call_details &cd) const |
1013 | { |
1014 | region_model_context *ctxt = cd.get_ctxt (); |
1015 | region_model *model = cd.get_model (); |
1016 | region_model_manager *mgr = cd.get_manager (); |
1017 | |
1018 | const svalue *in_ptr = cd.get_arg_svalue (idx: 0); |
1019 | const region *ap_reg |
1020 | = model->deref_rvalue (ptr_sval: in_ptr, ptr_tree: cd.get_arg_tree (idx: 0), ctxt); |
1021 | |
1022 | const svalue *ap_sval = model->get_store_value (reg: ap_reg, ctxt); |
1023 | if (const svalue *cast = ap_sval->maybe_undo_cast ()) |
1024 | ap_sval = cast; |
1025 | |
1026 | tree va_list_tree = get_va_list_diag_arg (va_list_tree: cd.get_arg_tree (idx: 0)); |
1027 | ap_sval = model->check_for_poison (sval: ap_sval, expr: va_list_tree, src_region: ap_reg, ctxt); |
1028 | |
1029 | cd.set_any_lhs_with_defaults (); |
1030 | |
1031 | if (const region *impl_reg = ap_sval->maybe_get_region ()) |
1032 | { |
1033 | const svalue *old_impl_sval = model->get_store_value (reg: impl_reg, ctxt); |
1034 | if (const var_arg_region *arg_reg |
1035 | = maybe_get_var_arg_region (ap_sval: old_impl_sval)) |
1036 | { |
1037 | bool saw_problem = false; |
1038 | |
1039 | const frame_region *frame_reg = arg_reg->get_frame_region (); |
1040 | unsigned next_arg_idx = arg_reg->get_index (); |
1041 | |
1042 | 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 | if (const svalue *arg_sval |
1050 | = model->get_store ()->get_any_binding |
1051 | (mgr: mgr->get_store_manager (), reg: arg_reg)) |
1052 | { |
1053 | tree lhs_type = cd.get_lhs_type (); |
1054 | tree arg_type = arg_sval->get_type (); |
1055 | if (va_arg_compatible_types_p (lhs_type, arg_type, arg_sval: *arg_sval)) |
1056 | cd.maybe_set_lhs (result: arg_sval); |
1057 | else |
1058 | { |
1059 | if (ctxt) |
1060 | ctxt->warn (d: make_unique <va_arg_type_mismatch> |
1061 | (args&: va_list_tree, |
1062 | args&: arg_reg, |
1063 | args&: lhs_type, |
1064 | args&: arg_type)); |
1065 | saw_problem = true; |
1066 | } |
1067 | } |
1068 | else |
1069 | { |
1070 | if (ctxt) |
1071 | ctxt->warn (d: make_unique <va_list_exhausted> (args&: va_list_tree, |
1072 | args&: 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 | gcc_assert (frame_reg->get_stack_depth () == 1); |
1083 | } |
1084 | |
1085 | if (saw_problem) |
1086 | { |
1087 | /* Set impl_reg to UNKNOWN to suppress further warnings. */ |
1088 | const svalue *new_ap_sval |
1089 | = mgr->get_or_create_unknown_svalue (type: impl_reg->get_type ()); |
1090 | model->set_value (lhs_reg: impl_reg, rhs_sval: new_ap_sval, ctxt); |
1091 | } |
1092 | else |
1093 | { |
1094 | /* Update impl_reg to advance to the next arg. */ |
1095 | const region *next_var_arg_region |
1096 | = mgr->get_var_arg_region (parent: frame_reg, idx: next_arg_idx + 1); |
1097 | const svalue *new_ap_sval |
1098 | = mgr->get_ptr_svalue (NULL_TREE, pointee: next_var_arg_region); |
1099 | model->set_value (lhs_reg: impl_reg, rhs_sval: new_ap_sval, ctxt); |
1100 | } |
1101 | } |
1102 | } |
1103 | } |
1104 | |
1105 | /* Handler for "__builtin_va_end". */ |
1106 | |
1107 | class kf_va_end : public known_function |
1108 | { |
1109 | public: |
1110 | bool matches_call_types_p (const call_details &) const |
1111 | { |
1112 | return true; |
1113 | } |
1114 | }; |
1115 | |
1116 | /* Populate KFM with instances of known functions relating to varargs. */ |
1117 | |
1118 | void |
1119 | register_varargs_builtins (known_function_manager &kfm) |
1120 | { |
1121 | kfm.add (name: BUILT_IN_VA_START, kf: make_unique<kf_va_start> ()); |
1122 | kfm.add (name: BUILT_IN_VA_COPY, kf: make_unique<kf_va_copy> ()); |
1123 | kfm.add (ifn: IFN_VA_ARG, kf: make_unique<kf_va_arg> ()); |
1124 | kfm.add (name: BUILT_IN_VA_END, kf: make_unique<kf_va_end> ()); |
1125 | } |
1126 | |
1127 | } // namespace ana |
1128 | |
1129 | #endif /* #if ENABLE_ANALYZER */ |
1130 | |