1 | /* Handling for the known behavior of various specific functions. |
2 | Copyright (C) 2020-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 "tree.h" |
26 | #include "function.h" |
27 | #include "basic-block.h" |
28 | #include "gimple.h" |
29 | #include "diagnostic-core.h" |
30 | #include "diagnostic-metadata.h" |
31 | #include "analyzer/analyzer.h" |
32 | #include "analyzer/analyzer-logging.h" |
33 | #include "diagnostic.h" |
34 | #include "analyzer/region-model.h" |
35 | #include "analyzer/call-details.h" |
36 | #include "analyzer/call-info.h" |
37 | #include "make-unique.h" |
38 | |
39 | #if ENABLE_ANALYZER |
40 | |
41 | namespace ana { |
42 | |
43 | /* Abstract subclass for describing undefined behavior of an API. */ |
44 | |
45 | class undefined_function_behavior |
46 | : public pending_diagnostic_subclass<undefined_function_behavior> |
47 | { |
48 | public: |
49 | undefined_function_behavior (const call_details &cd) |
50 | : m_call_stmt (cd.get_call_stmt ()), |
51 | m_callee_fndecl (cd.get_fndecl_for_call ()) |
52 | { |
53 | gcc_assert (m_call_stmt); |
54 | gcc_assert (m_callee_fndecl); |
55 | } |
56 | |
57 | const char *get_kind () const final override |
58 | { |
59 | return "undefined_behavior" ; |
60 | } |
61 | |
62 | bool operator== (const undefined_function_behavior &other) const |
63 | { |
64 | return (m_call_stmt == other.m_call_stmt |
65 | && m_callee_fndecl == other.m_callee_fndecl); |
66 | } |
67 | |
68 | bool terminate_path_p () const final override { return true; } |
69 | |
70 | tree get_callee_fndecl () const { return m_callee_fndecl; } |
71 | |
72 | private: |
73 | const gimple *m_call_stmt; |
74 | tree m_callee_fndecl; |
75 | }; |
76 | |
77 | /* class pure_known_function_with_default_return : public known_function. */ |
78 | |
79 | void |
80 | pure_known_function_with_default_return:: |
81 | impl_call_pre (const call_details &cd) const |
82 | { |
83 | cd.set_any_lhs_with_defaults (); |
84 | } |
85 | |
86 | /* Implementations of specific functions. */ |
87 | |
88 | /* Handler for "alloca". */ |
89 | |
90 | class kf_alloca : public builtin_known_function |
91 | { |
92 | public: |
93 | bool matches_call_types_p (const call_details &cd) const final override |
94 | { |
95 | return cd.num_args () == 1; |
96 | } |
97 | enum built_in_function builtin_code () const final override |
98 | { |
99 | return BUILT_IN_ALLOCA; |
100 | } |
101 | void impl_call_pre (const call_details &cd) const final override; |
102 | }; |
103 | |
104 | void |
105 | kf_alloca::impl_call_pre (const call_details &cd) const |
106 | { |
107 | const svalue *size_sval = cd.get_arg_svalue (idx: 0); |
108 | |
109 | region_model *model = cd.get_model (); |
110 | region_model_manager *mgr = cd.get_manager (); |
111 | |
112 | const region *new_reg |
113 | = model->create_region_for_alloca (size_in_bytes: size_sval, ctxt: cd.get_ctxt ()); |
114 | const svalue *ptr_sval |
115 | = mgr->get_ptr_svalue (ptr_type: cd.get_lhs_type (), pointee: new_reg); |
116 | cd.maybe_set_lhs (result: ptr_sval); |
117 | } |
118 | |
119 | /* Handler for __atomic_exchange. |
120 | Although the user-facing documentation specifies it as having this |
121 | signature: |
122 | void __atomic_exchange (type *ptr, type *val, type *ret, int memorder) |
123 | |
124 | by the time the C/C++ frontends have acted on it, any calls that |
125 | can't be mapped to a _N variation end up with this signature: |
126 | |
127 | void |
128 | __atomic_exchange (size_t sz, void *ptr, void *val, void *ret, |
129 | int memorder) |
130 | |
131 | as seen in the gimple seen by the analyzer, and as specified |
132 | in sync-builtins.def. */ |
133 | |
134 | class kf_atomic_exchange : public internal_known_function |
135 | { |
136 | public: |
137 | /* This is effectively: |
138 | tmpA = *PTR; |
139 | tmpB = *VAL; |
140 | *PTR = tmpB; |
141 | *RET = tmpA; |
142 | */ |
143 | void impl_call_pre (const call_details &cd) const final override |
144 | { |
145 | const svalue *num_bytes_sval = cd.get_arg_svalue (idx: 0); |
146 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 1); |
147 | tree ptr_tree = cd.get_arg_tree (idx: 1); |
148 | const svalue *val_sval = cd.get_arg_svalue (idx: 2); |
149 | tree val_tree = cd.get_arg_tree (idx: 2); |
150 | const svalue *ret_sval = cd.get_arg_svalue (idx: 3); |
151 | tree ret_tree = cd.get_arg_tree (idx: 3); |
152 | /* Ignore the memorder param. */ |
153 | |
154 | region_model *model = cd.get_model (); |
155 | region_model_context *ctxt = cd.get_ctxt (); |
156 | |
157 | const region *ptr_reg = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
158 | const region *val_reg = model->deref_rvalue (ptr_sval: val_sval, ptr_tree: val_tree, ctxt); |
159 | const region *ret_reg = model->deref_rvalue (ptr_sval: ret_sval, ptr_tree: ret_tree, ctxt); |
160 | |
161 | const svalue *tmp_a_sval |
162 | = model->read_bytes (src_reg: ptr_reg, src_ptr_expr: ptr_tree, num_bytes_sval, ctxt); |
163 | const svalue *tmp_b_sval |
164 | = model->read_bytes (src_reg: val_reg, src_ptr_expr: val_tree, num_bytes_sval, ctxt); |
165 | model->write_bytes (dest_reg: ptr_reg, num_bytes_sval, sval: tmp_b_sval, ctxt); |
166 | model->write_bytes (dest_reg: ret_reg, num_bytes_sval, sval: tmp_a_sval, ctxt); |
167 | } |
168 | }; |
169 | |
170 | /* Handler for: |
171 | __atomic_exchange_n (type *ptr, type val, int memorder). */ |
172 | |
173 | class kf_atomic_exchange_n : public internal_known_function |
174 | { |
175 | public: |
176 | /* This is effectively: |
177 | RET = *PTR; |
178 | *PTR = VAL; |
179 | return RET; |
180 | */ |
181 | void impl_call_pre (const call_details &cd) const final override |
182 | { |
183 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 0); |
184 | tree ptr_tree = cd.get_arg_tree (idx: 0); |
185 | const svalue *set_sval = cd.get_arg_svalue (idx: 1); |
186 | /* Ignore the memorder param. */ |
187 | |
188 | region_model *model = cd.get_model (); |
189 | region_model_context *ctxt = cd.get_ctxt (); |
190 | |
191 | const region *dst_region = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
192 | const svalue *ret_sval = model->get_store_value (reg: dst_region, ctxt); |
193 | model->set_value (lhs_reg: dst_region, rhs_sval: set_sval, ctxt); |
194 | cd.maybe_set_lhs (result: ret_sval); |
195 | } |
196 | }; |
197 | |
198 | /* Handler for: |
199 | type __atomic_fetch_add (type *ptr, type val, int memorder); |
200 | type __atomic_fetch_sub (type *ptr, type val, int memorder); |
201 | type __atomic_fetch_and (type *ptr, type val, int memorder); |
202 | type __atomic_fetch_xor (type *ptr, type val, int memorder); |
203 | type __atomic_fetch_or (type *ptr, type val, int memorder); |
204 | */ |
205 | |
206 | class kf_atomic_fetch_op : public internal_known_function |
207 | { |
208 | public: |
209 | kf_atomic_fetch_op (enum tree_code op): m_op (op) {} |
210 | |
211 | /* This is effectively: |
212 | RET = *PTR; |
213 | *PTR = RET OP VAL; |
214 | return RET; |
215 | */ |
216 | void impl_call_pre (const call_details &cd) const final override |
217 | { |
218 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 0); |
219 | tree ptr_tree = cd.get_arg_tree (idx: 0); |
220 | const svalue *val_sval = cd.get_arg_svalue (idx: 1); |
221 | /* Ignore the memorder param. */ |
222 | |
223 | region_model *model = cd.get_model (); |
224 | region_model_manager *mgr = cd.get_manager (); |
225 | region_model_context *ctxt = cd.get_ctxt (); |
226 | |
227 | const region *star_ptr_region |
228 | = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
229 | const svalue *old_sval = model->get_store_value (reg: star_ptr_region, ctxt); |
230 | const svalue *new_sval = mgr->get_or_create_binop (type: old_sval->get_type (), |
231 | op: m_op, |
232 | arg0: old_sval, arg1: val_sval); |
233 | model->set_value (lhs_reg: star_ptr_region, rhs_sval: new_sval, ctxt); |
234 | cd.maybe_set_lhs (result: old_sval); |
235 | } |
236 | |
237 | private: |
238 | enum tree_code m_op; |
239 | }; |
240 | |
241 | /* Handler for: |
242 | type __atomic_add_fetch (type *ptr, type val, int memorder); |
243 | type __atomic_sub_fetch (type *ptr, type val, int memorder); |
244 | type __atomic_and_fetch (type *ptr, type val, int memorder); |
245 | type __atomic_xor_fetch (type *ptr, type val, int memorder); |
246 | type __atomic_or_fetch (type *ptr, type val, int memorder); |
247 | */ |
248 | |
249 | class kf_atomic_op_fetch : public internal_known_function |
250 | { |
251 | public: |
252 | kf_atomic_op_fetch (enum tree_code op): m_op (op) {} |
253 | |
254 | /* This is effectively: |
255 | *PTR = RET OP VAL; |
256 | return *PTR; |
257 | */ |
258 | void impl_call_pre (const call_details &cd) const final override |
259 | { |
260 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 0); |
261 | tree ptr_tree = cd.get_arg_tree (idx: 0); |
262 | const svalue *val_sval = cd.get_arg_svalue (idx: 1); |
263 | /* Ignore the memorder param. */ |
264 | |
265 | region_model *model = cd.get_model (); |
266 | region_model_manager *mgr = cd.get_manager (); |
267 | region_model_context *ctxt = cd.get_ctxt (); |
268 | |
269 | const region *star_ptr_region |
270 | = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
271 | const svalue *old_sval = model->get_store_value (reg: star_ptr_region, ctxt); |
272 | const svalue *new_sval = mgr->get_or_create_binop (type: old_sval->get_type (), |
273 | op: m_op, |
274 | arg0: old_sval, arg1: val_sval); |
275 | model->set_value (lhs_reg: star_ptr_region, rhs_sval: new_sval, ctxt); |
276 | cd.maybe_set_lhs (result: new_sval); |
277 | } |
278 | |
279 | private: |
280 | enum tree_code m_op; |
281 | }; |
282 | |
283 | /* Handler for __atomic_load. |
284 | Although the user-facing documentation specifies it as having this |
285 | signature: |
286 | |
287 | void __atomic_load (type *ptr, type *ret, int memorder) |
288 | |
289 | by the time the C/C++ frontends have acted on it, any calls that |
290 | can't be mapped to a _N variation end up with this signature: |
291 | |
292 | void __atomic_load (size_t sz, const void *src, void *dst, int memorder); |
293 | |
294 | as seen in the gimple seen by the analyzer, and as specified |
295 | in sync-builtins.def. */ |
296 | |
297 | class kf_atomic_load : public internal_known_function |
298 | { |
299 | public: |
300 | /* This is effectively: |
301 | memmove (dst, src, sz); |
302 | */ |
303 | void impl_call_pre (const call_details &cd) const final override |
304 | { |
305 | const svalue *num_bytes_sval = cd.get_arg_svalue (idx: 0); |
306 | const svalue *src_sval = cd.get_arg_svalue (idx: 1); |
307 | tree src_tree = cd.get_arg_tree (idx: 1); |
308 | const svalue *dst_sval = cd.get_arg_svalue (idx: 2); |
309 | tree dst_tree = cd.get_arg_tree (idx: 2); |
310 | /* Ignore the memorder param. */ |
311 | |
312 | region_model *model = cd.get_model (); |
313 | region_model_context *ctxt = cd.get_ctxt (); |
314 | |
315 | const region *dst_reg = model->deref_rvalue (ptr_sval: dst_sval, ptr_tree: dst_tree, ctxt); |
316 | const region *src_reg = model->deref_rvalue (ptr_sval: src_sval, ptr_tree: src_tree, ctxt); |
317 | |
318 | const svalue *data_sval |
319 | = model->read_bytes (src_reg, src_ptr_expr: src_tree, num_bytes_sval, ctxt); |
320 | model->write_bytes (dest_reg: dst_reg, num_bytes_sval, sval: data_sval, ctxt); |
321 | } |
322 | }; |
323 | |
324 | /* Handler for __atomic_store. |
325 | Although the user-facing documentation specifies it as having this |
326 | signature: |
327 | |
328 | void __atomic_store (type *ptr, type *val, int memorder) |
329 | |
330 | by the time the C/C++ frontends have acted on it, any calls that |
331 | can't be mapped to a _N variation end up with this signature: |
332 | |
333 | void __atomic_store (size_t sz, type *dst, type *src, int memorder) |
334 | |
335 | as seen in the gimple seen by the analyzer, and as specified |
336 | in sync-builtins.def. */ |
337 | |
338 | class kf_atomic_store : public internal_known_function |
339 | { |
340 | public: |
341 | /* This is effectively: |
342 | memmove (dst, src, sz); |
343 | */ |
344 | void impl_call_pre (const call_details &cd) const final override |
345 | { |
346 | const svalue *num_bytes_sval = cd.get_arg_svalue (idx: 0); |
347 | const svalue *dst_sval = cd.get_arg_svalue (idx: 1); |
348 | tree dst_tree = cd.get_arg_tree (idx: 1); |
349 | const svalue *src_sval = cd.get_arg_svalue (idx: 2); |
350 | tree src_tree = cd.get_arg_tree (idx: 2); |
351 | /* Ignore the memorder param. */ |
352 | |
353 | region_model *model = cd.get_model (); |
354 | region_model_context *ctxt = cd.get_ctxt (); |
355 | |
356 | const region *dst_reg = model->deref_rvalue (ptr_sval: dst_sval, ptr_tree: dst_tree, ctxt); |
357 | const region *src_reg = model->deref_rvalue (ptr_sval: src_sval, ptr_tree: src_tree, ctxt); |
358 | |
359 | const svalue *data_sval |
360 | = model->read_bytes (src_reg, src_ptr_expr: src_tree, num_bytes_sval, ctxt); |
361 | model->write_bytes (dest_reg: dst_reg, num_bytes_sval, sval: data_sval, ctxt); |
362 | } |
363 | }; |
364 | |
365 | /* Handler for: |
366 | type __atomic_load_n (type *ptr, int memorder) */ |
367 | |
368 | class kf_atomic_load_n : public internal_known_function |
369 | { |
370 | public: |
371 | /* This is effectively: |
372 | RET = *PTR; |
373 | return RET; |
374 | */ |
375 | void impl_call_pre (const call_details &cd) const final override |
376 | { |
377 | const svalue *ptr_ptr_sval = cd.get_arg_svalue (idx: 0); |
378 | tree ptr_ptr_tree = cd.get_arg_tree (idx: 0); |
379 | /* Ignore the memorder param. */ |
380 | |
381 | region_model *model = cd.get_model (); |
382 | region_model_context *ctxt = cd.get_ctxt (); |
383 | |
384 | const region *ptr_region |
385 | = model->deref_rvalue (ptr_sval: ptr_ptr_sval, ptr_tree: ptr_ptr_tree, ctxt); |
386 | const svalue *star_ptr_sval = model->get_store_value (reg: ptr_region, ctxt); |
387 | cd.maybe_set_lhs (result: star_ptr_sval); |
388 | } |
389 | }; |
390 | |
391 | /* Handler for: |
392 | void __atomic_store_n (type *ptr, type val, int memorder) */ |
393 | |
394 | class kf_atomic_store_n : public internal_known_function |
395 | { |
396 | public: |
397 | /* This is effectively: |
398 | *PTR = VAL; |
399 | */ |
400 | void impl_call_pre (const call_details &cd) const final override |
401 | { |
402 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 0); |
403 | tree ptr_tree = cd.get_arg_tree (idx: 0); |
404 | const svalue *new_sval = cd.get_arg_svalue (idx: 1); |
405 | /* Ignore the memorder param. */ |
406 | |
407 | region_model *model = cd.get_model (); |
408 | region_model_context *ctxt = cd.get_ctxt (); |
409 | |
410 | const region *star_ptr_region |
411 | = model->deref_rvalue (ptr_sval, ptr_tree, ctxt); |
412 | model->set_value (lhs_reg: star_ptr_region, rhs_sval: new_sval, ctxt); |
413 | } |
414 | }; |
415 | |
416 | /* Handler for "__builtin_expect" etc. */ |
417 | |
418 | class kf_expect : public internal_known_function |
419 | { |
420 | public: |
421 | void impl_call_pre (const call_details &cd) const final override |
422 | { |
423 | /* __builtin_expect's return value is its initial argument. */ |
424 | const svalue *sval = cd.get_arg_svalue (idx: 0); |
425 | cd.maybe_set_lhs (result: sval); |
426 | } |
427 | }; |
428 | |
429 | /* Handler for "calloc". */ |
430 | |
431 | class kf_calloc : public builtin_known_function |
432 | { |
433 | public: |
434 | bool matches_call_types_p (const call_details &cd) const final override |
435 | { |
436 | return (cd.num_args () == 2 |
437 | && cd.arg_is_size_p (idx: 0) |
438 | && cd.arg_is_size_p (idx: 1)); |
439 | } |
440 | enum built_in_function builtin_code () const final override |
441 | { |
442 | return BUILT_IN_CALLOC; |
443 | } |
444 | |
445 | void impl_call_pre (const call_details &cd) const final override; |
446 | }; |
447 | |
448 | void |
449 | kf_calloc::impl_call_pre (const call_details &cd) const |
450 | { |
451 | region_model *model = cd.get_model (); |
452 | region_model_manager *mgr = cd.get_manager (); |
453 | const svalue *nmemb_sval = cd.get_arg_svalue (idx: 0); |
454 | const svalue *size_sval = cd.get_arg_svalue (idx: 1); |
455 | /* TODO: check for overflow here? */ |
456 | const svalue *prod_sval |
457 | = mgr->get_or_create_binop (size_type_node, op: MULT_EXPR, |
458 | arg0: nmemb_sval, arg1: size_sval); |
459 | const region *new_reg |
460 | = model->get_or_create_region_for_heap_alloc (size_in_bytes: prod_sval, ctxt: cd.get_ctxt ()); |
461 | const region *sized_reg |
462 | = mgr->get_sized_region (parent: new_reg, NULL_TREE, byte_size_sval: prod_sval); |
463 | model->zero_fill_region (reg: sized_reg, ctxt: cd.get_ctxt ()); |
464 | if (cd.get_lhs_type ()) |
465 | { |
466 | const svalue *ptr_sval |
467 | = mgr->get_ptr_svalue (ptr_type: cd.get_lhs_type (), pointee: new_reg); |
468 | cd.maybe_set_lhs (result: ptr_sval); |
469 | } |
470 | } |
471 | |
472 | /* Handler for glibc's "__errno_location". */ |
473 | |
474 | class kf_errno_location : public known_function |
475 | { |
476 | public: |
477 | bool matches_call_types_p (const call_details &cd) const final override |
478 | { |
479 | return cd.num_args () == 0; |
480 | } |
481 | |
482 | void impl_call_pre (const call_details &cd) const final override |
483 | { |
484 | if (cd.get_lhs_region ()) |
485 | { |
486 | region_model_manager *mgr = cd.get_manager (); |
487 | const region *errno_reg = mgr->get_errno_region (); |
488 | const svalue *errno_ptr = mgr->get_ptr_svalue (ptr_type: cd.get_lhs_type (), |
489 | pointee: errno_reg); |
490 | cd.maybe_set_lhs (result: errno_ptr); |
491 | } |
492 | } |
493 | }; |
494 | |
495 | /* Handler for "error" and "error_at_line" from GNU's non-standard <error.h>. |
496 | MIN_ARGS identifies the minimum number of expected arguments |
497 | to be consistent with such a call (3 and 5 respectively). */ |
498 | |
499 | class kf_error : public known_function |
500 | { |
501 | public: |
502 | kf_error (unsigned min_args) : m_min_args (min_args) {} |
503 | |
504 | bool matches_call_types_p (const call_details &cd) const final override |
505 | { |
506 | return (cd.num_args () >= m_min_args |
507 | && cd.get_arg_type (idx: 0) == integer_type_node); |
508 | } |
509 | |
510 | void impl_call_pre (const call_details &cd) const final override; |
511 | |
512 | private: |
513 | unsigned m_min_args; |
514 | }; |
515 | |
516 | void |
517 | kf_error::impl_call_pre (const call_details &cd) const |
518 | { |
519 | /* The process exits if status != 0, so it only continues |
520 | for the case where status == 0. |
521 | Add that constraint, or terminate this analysis path. */ |
522 | tree status = cd.get_arg_tree (idx: 0); |
523 | region_model_context *ctxt = cd.get_ctxt (); |
524 | region_model *model = cd.get_model (); |
525 | if (!model->add_constraint (lhs: status, op: EQ_EXPR, integer_zero_node, ctxt)) |
526 | if (ctxt) |
527 | ctxt->terminate_path (); |
528 | |
529 | /* Check "format" arg. */ |
530 | const int fmt_arg_idx = (m_min_args == 3) ? 2 : 4; |
531 | model->check_for_null_terminated_string_arg (cd, idx: fmt_arg_idx); |
532 | } |
533 | |
534 | /* Handler for fopen. |
535 | FILE *fopen (const char *filename, const char *mode); |
536 | See e.g. https://en.cppreference.com/w/c/io/fopen |
537 | https://www.man7.org/linux/man-pages/man3/fopen.3.html |
538 | https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170 */ |
539 | |
540 | class kf_fopen : public known_function |
541 | { |
542 | public: |
543 | bool matches_call_types_p (const call_details &cd) const final override |
544 | { |
545 | return (cd.num_args () == 2 |
546 | && cd.arg_is_pointer_p (idx: 0) |
547 | && cd.arg_is_pointer_p (idx: 1)); |
548 | } |
549 | |
550 | void impl_call_pre (const call_details &cd) const final override |
551 | { |
552 | cd.check_for_null_terminated_string_arg (arg_idx: 0); |
553 | cd.check_for_null_terminated_string_arg (arg_idx: 1); |
554 | cd.set_any_lhs_with_defaults (); |
555 | |
556 | /* fopen's mode param is effectively a mini-DSL, but there are various |
557 | non-standard extensions, so we don't bother to check it. */ |
558 | } |
559 | }; |
560 | |
561 | /* Handler for "free", after sm-handling. |
562 | |
563 | If the ptr points to an underlying heap region, delete the region, |
564 | poisoning pointers to it and regions within it. |
565 | |
566 | We delay this until after sm-state has been updated so that the |
567 | sm-handling can transition all of the various casts of the pointer |
568 | to a "freed" state *before* we delete the related region here. |
569 | |
570 | This has to be done here so that the sm-handling can use the fact |
571 | that they point to the same region to establish that they are equal |
572 | (in region_model::eval_condition), and thus transition |
573 | all pointers to the region to the "freed" state together, regardless |
574 | of casts. */ |
575 | |
576 | class kf_free : public builtin_known_function |
577 | { |
578 | public: |
579 | bool matches_call_types_p (const call_details &cd) const final override |
580 | { |
581 | return (cd.num_args () == 1 && cd.arg_is_pointer_p (idx: 0)); |
582 | } |
583 | enum built_in_function builtin_code () const final override |
584 | { |
585 | return BUILT_IN_FREE; |
586 | } |
587 | void impl_call_post (const call_details &cd) const final override; |
588 | }; |
589 | |
590 | void |
591 | kf_free::impl_call_post (const call_details &cd) const |
592 | { |
593 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 0); |
594 | if (const region *freed_reg = ptr_sval->maybe_get_region ()) |
595 | { |
596 | /* If the ptr points to an underlying heap region, delete it, |
597 | poisoning pointers. */ |
598 | region_model *model = cd.get_model (); |
599 | model->unbind_region_and_descendents (reg: freed_reg, pkind: POISON_KIND_FREED); |
600 | model->unset_dynamic_extents (reg: freed_reg); |
601 | } |
602 | } |
603 | |
604 | /* Handle the on_call_pre part of "malloc". */ |
605 | |
606 | class kf_malloc : public builtin_known_function |
607 | { |
608 | public: |
609 | bool matches_call_types_p (const call_details &cd) const final override |
610 | { |
611 | return (cd.num_args () == 1 |
612 | && cd.arg_is_size_p (idx: 0)); |
613 | } |
614 | enum built_in_function builtin_code () const final override |
615 | { |
616 | return BUILT_IN_MALLOC; |
617 | } |
618 | void impl_call_pre (const call_details &cd) const final override; |
619 | }; |
620 | |
621 | void |
622 | kf_malloc::impl_call_pre (const call_details &cd) const |
623 | { |
624 | region_model *model = cd.get_model (); |
625 | region_model_manager *mgr = cd.get_manager (); |
626 | const svalue *size_sval = cd.get_arg_svalue (idx: 0); |
627 | const region *new_reg |
628 | = model->get_or_create_region_for_heap_alloc (size_in_bytes: size_sval, ctxt: cd.get_ctxt ()); |
629 | if (cd.get_lhs_type ()) |
630 | { |
631 | const svalue *ptr_sval |
632 | = mgr->get_ptr_svalue (ptr_type: cd.get_lhs_type (), pointee: new_reg); |
633 | cd.maybe_set_lhs (result: ptr_sval); |
634 | } |
635 | } |
636 | |
637 | /* Handler for "memcpy" and "__builtin_memcpy", |
638 | "memmove", and "__builtin_memmove". */ |
639 | |
640 | class kf_memcpy_memmove : public builtin_known_function |
641 | { |
642 | public: |
643 | enum kf_memcpy_memmove_variant |
644 | { |
645 | KF_MEMCPY, |
646 | KF_MEMCPY_CHK, |
647 | KF_MEMMOVE, |
648 | KF_MEMMOVE_CHK, |
649 | }; |
650 | kf_memcpy_memmove (enum kf_memcpy_memmove_variant variant) |
651 | : m_variant (variant) {}; |
652 | bool matches_call_types_p (const call_details &cd) const final override |
653 | { |
654 | return (cd.num_args () == 3 |
655 | && cd.arg_is_pointer_p (idx: 0) |
656 | && cd.arg_is_pointer_p (idx: 1) |
657 | && cd.arg_is_size_p (idx: 2)); |
658 | } |
659 | enum built_in_function builtin_code () const final override |
660 | { |
661 | switch (m_variant) |
662 | { |
663 | case KF_MEMCPY: |
664 | return BUILT_IN_MEMCPY; |
665 | case KF_MEMCPY_CHK: |
666 | return BUILT_IN_MEMCPY_CHK; |
667 | case KF_MEMMOVE: |
668 | return BUILT_IN_MEMMOVE; |
669 | case KF_MEMMOVE_CHK: |
670 | return BUILT_IN_MEMMOVE_CHK; |
671 | default: |
672 | gcc_unreachable (); |
673 | } |
674 | } |
675 | void impl_call_pre (const call_details &cd) const final override; |
676 | private: |
677 | const enum kf_memcpy_memmove_variant m_variant; |
678 | }; |
679 | |
680 | void |
681 | kf_memcpy_memmove::impl_call_pre (const call_details &cd) const |
682 | { |
683 | const svalue *dest_ptr_sval = cd.get_arg_svalue (idx: 0); |
684 | const svalue *src_ptr_sval = cd.get_arg_svalue (idx: 1); |
685 | const svalue *num_bytes_sval = cd.get_arg_svalue (idx: 2); |
686 | |
687 | region_model *model = cd.get_model (); |
688 | |
689 | const region *dest_reg |
690 | = model->deref_rvalue (ptr_sval: dest_ptr_sval, ptr_tree: cd.get_arg_tree (idx: 0), ctxt: cd.get_ctxt ()); |
691 | const region *src_reg |
692 | = model->deref_rvalue (ptr_sval: src_ptr_sval, ptr_tree: cd.get_arg_tree (idx: 1), ctxt: cd.get_ctxt ()); |
693 | |
694 | cd.maybe_set_lhs (result: dest_ptr_sval); |
695 | /* Check for overlap. */ |
696 | switch (m_variant) |
697 | { |
698 | case KF_MEMCPY: |
699 | case KF_MEMCPY_CHK: |
700 | cd.complain_about_overlap (arg_idx_a: 0, arg_idx_b: 1, num_bytes_read_sval: num_bytes_sval); |
701 | break; |
702 | |
703 | case KF_MEMMOVE: |
704 | case KF_MEMMOVE_CHK: |
705 | /* It's OK for memmove's arguments to overlap. */ |
706 | break; |
707 | |
708 | default: |
709 | gcc_unreachable (); |
710 | } |
711 | model->copy_bytes (dest_reg, |
712 | src_reg, src_ptr_expr: cd.get_arg_tree (idx: 1), |
713 | num_bytes_sval, |
714 | ctxt: cd.get_ctxt ()); |
715 | } |
716 | |
717 | /* Handler for "memset" and "__builtin_memset". */ |
718 | |
719 | class kf_memset : public builtin_known_function |
720 | { |
721 | public: |
722 | kf_memset (bool chk_variant) : m_chk_variant (chk_variant) {} |
723 | bool matches_call_types_p (const call_details &cd) const final override |
724 | { |
725 | return (cd.num_args () == 3 && cd.arg_is_pointer_p (idx: 0)); |
726 | } |
727 | enum built_in_function builtin_code () const final override |
728 | { |
729 | return m_chk_variant ? BUILT_IN_MEMSET_CHK : BUILT_IN_MEMSET; |
730 | } |
731 | void impl_call_pre (const call_details &cd) const final override; |
732 | private: |
733 | const bool m_chk_variant; |
734 | }; |
735 | |
736 | void |
737 | kf_memset::impl_call_pre (const call_details &cd) const |
738 | { |
739 | const svalue *dest_sval = cd.get_arg_svalue (idx: 0); |
740 | const svalue *fill_value_sval = cd.get_arg_svalue (idx: 1); |
741 | const svalue *num_bytes_sval = cd.get_arg_svalue (idx: 2); |
742 | |
743 | region_model *model = cd.get_model (); |
744 | region_model_manager *mgr = cd.get_manager (); |
745 | |
746 | const region *dest_reg |
747 | = model->deref_rvalue (ptr_sval: dest_sval, ptr_tree: cd.get_arg_tree (idx: 0), ctxt: cd.get_ctxt ()); |
748 | |
749 | const svalue *fill_value_u8 |
750 | = mgr->get_or_create_cast (unsigned_char_type_node, arg: fill_value_sval); |
751 | |
752 | const region *sized_dest_reg = mgr->get_sized_region (parent: dest_reg, |
753 | NULL_TREE, |
754 | byte_size_sval: num_bytes_sval); |
755 | model->fill_region (reg: sized_dest_reg, sval: fill_value_u8, ctxt: cd.get_ctxt ()); |
756 | |
757 | cd.maybe_set_lhs (result: dest_sval); |
758 | } |
759 | |
760 | /* A subclass of pending_diagnostic for complaining about 'putenv' |
761 | called on an auto var. */ |
762 | |
763 | class putenv_of_auto_var |
764 | : public pending_diagnostic_subclass<putenv_of_auto_var> |
765 | { |
766 | public: |
767 | putenv_of_auto_var (tree fndecl, const region *reg) |
768 | : m_fndecl (fndecl), m_reg (reg), |
769 | m_var_decl (reg->get_base_region ()->maybe_get_decl ()) |
770 | { |
771 | } |
772 | |
773 | const char *get_kind () const final override |
774 | { |
775 | return "putenv_of_auto_var" ; |
776 | } |
777 | |
778 | bool operator== (const putenv_of_auto_var &other) const |
779 | { |
780 | return (m_fndecl == other.m_fndecl |
781 | && m_reg == other.m_reg |
782 | && same_tree_p (t1: m_var_decl, t2: other.m_var_decl)); |
783 | } |
784 | |
785 | int get_controlling_option () const final override |
786 | { |
787 | return OPT_Wanalyzer_putenv_of_auto_var; |
788 | } |
789 | |
790 | bool emit (diagnostic_emission_context &ctxt) final override |
791 | { |
792 | auto_diagnostic_group d; |
793 | |
794 | /* SEI CERT C Coding Standard: "POS34-C. Do not call putenv() with a |
795 | pointer to an automatic variable as the argument". */ |
796 | diagnostic_metadata::precanned_rule |
797 | rule ("POS34-C" , "https://wiki.sei.cmu.edu/confluence/x/6NYxBQ" ); |
798 | ctxt.add_rule (r: rule); |
799 | |
800 | bool warned; |
801 | if (m_var_decl) |
802 | warned = ctxt.warn ("%qE on a pointer to automatic variable %qE" , |
803 | m_fndecl, m_var_decl); |
804 | else |
805 | warned = ctxt.warn ("%qE on a pointer to an on-stack buffer" , |
806 | m_fndecl); |
807 | if (warned) |
808 | { |
809 | if (m_var_decl) |
810 | inform (DECL_SOURCE_LOCATION (m_var_decl), |
811 | "%qE declared on stack here" , m_var_decl); |
812 | inform (ctxt.get_location (), "perhaps use %qs rather than %qE" , |
813 | "setenv" , m_fndecl); |
814 | } |
815 | |
816 | return warned; |
817 | } |
818 | |
819 | label_text describe_final_event (const evdesc::final_event &ev) final override |
820 | { |
821 | if (m_var_decl) |
822 | return ev.formatted_print (fmt: "%qE on a pointer to automatic variable %qE" , |
823 | m_fndecl, m_var_decl); |
824 | else |
825 | return ev.formatted_print (fmt: "%qE on a pointer to an on-stack buffer" , |
826 | m_fndecl); |
827 | } |
828 | |
829 | void mark_interesting_stuff (interesting_t *interest) final override |
830 | { |
831 | if (!m_var_decl) |
832 | interest->add_region_creation (reg: m_reg->get_base_region ()); |
833 | } |
834 | |
835 | private: |
836 | tree m_fndecl; // non-NULL |
837 | const region *m_reg; // non-NULL |
838 | tree m_var_decl; // could be NULL |
839 | }; |
840 | |
841 | /* Handler for calls to "putenv". |
842 | |
843 | In theory we could try to model the state of the environment variables |
844 | for the process; for now we merely complain about putenv of regions |
845 | on the stack. */ |
846 | |
847 | class kf_putenv : public known_function |
848 | { |
849 | public: |
850 | bool matches_call_types_p (const call_details &cd) const final override |
851 | { |
852 | return (cd.num_args () == 1 && cd.arg_is_pointer_p (idx: 0)); |
853 | } |
854 | |
855 | void impl_call_pre (const call_details &cd) const final override |
856 | { |
857 | tree fndecl = cd.get_fndecl_for_call (); |
858 | gcc_assert (fndecl); |
859 | region_model_context *ctxt = cd.get_ctxt (); |
860 | region_model *model = cd.get_model (); |
861 | model->check_for_null_terminated_string_arg (cd, idx: 0); |
862 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 0); |
863 | const region *reg |
864 | = model->deref_rvalue (ptr_sval, ptr_tree: cd.get_arg_tree (idx: 0), ctxt); |
865 | model->get_store ()->mark_as_escaped (base_reg: reg); |
866 | enum memory_space mem_space = reg->get_memory_space (); |
867 | switch (mem_space) |
868 | { |
869 | default: |
870 | gcc_unreachable (); |
871 | case MEMSPACE_UNKNOWN: |
872 | case MEMSPACE_CODE: |
873 | case MEMSPACE_GLOBALS: |
874 | case MEMSPACE_HEAP: |
875 | case MEMSPACE_READONLY_DATA: |
876 | break; |
877 | case MEMSPACE_STACK: |
878 | if (ctxt) |
879 | ctxt->warn (d: make_unique<putenv_of_auto_var> (args&: fndecl, args&: reg)); |
880 | break; |
881 | } |
882 | cd.set_any_lhs_with_defaults (); |
883 | } |
884 | }; |
885 | |
886 | /* Handler for "realloc": |
887 | |
888 | void *realloc(void *ptr, size_t size); |
889 | |
890 | realloc(3) is awkward, since it has various different outcomes |
891 | that are best modelled as separate exploded nodes/edges. |
892 | |
893 | We first check for sm-state, in |
894 | malloc_state_machine::on_realloc_call, so that we |
895 | can complain about issues such as realloc of a non-heap |
896 | pointer, and terminate the path for such cases (and issue |
897 | the complaints at the call's exploded node). |
898 | |
899 | Assuming that these checks pass, we split the path here into |
900 | three special cases (and terminate the "standard" path): |
901 | (A) failure, returning NULL |
902 | (B) success, growing the buffer in-place without moving it |
903 | (C) success, allocating a new buffer, copying the content |
904 | of the old buffer to it, and freeing the old buffer. |
905 | |
906 | Each of these has a custom_edge_info subclass, which updates |
907 | the region_model and sm-state of the destination state. */ |
908 | |
909 | class kf_realloc : public builtin_known_function |
910 | { |
911 | public: |
912 | bool matches_call_types_p (const call_details &cd) const final override |
913 | { |
914 | return (cd.num_args () == 2 |
915 | && cd.arg_is_pointer_p (idx: 0) |
916 | && cd.arg_is_size_p (idx: 1)); |
917 | } |
918 | |
919 | enum built_in_function builtin_code () const final override |
920 | { |
921 | return BUILT_IN_REALLOC; |
922 | } |
923 | |
924 | void impl_call_post (const call_details &cd) const final override; |
925 | }; |
926 | |
927 | void |
928 | kf_realloc::impl_call_post (const call_details &cd) const |
929 | { |
930 | /* Three custom subclasses of custom_edge_info, for handling the various |
931 | outcomes of "realloc". */ |
932 | |
933 | /* Concrete custom_edge_info: a realloc call that fails, returning NULL. */ |
934 | class failure : public failed_call_info |
935 | { |
936 | public: |
937 | failure (const call_details &cd) |
938 | : failed_call_info (cd) |
939 | { |
940 | } |
941 | |
942 | bool update_model (region_model *model, |
943 | const exploded_edge *, |
944 | region_model_context *ctxt) const final override |
945 | { |
946 | /* Return NULL; everything else is unchanged. */ |
947 | const call_details cd (get_call_details (model, ctxt)); |
948 | region_model_manager *mgr = cd.get_manager (); |
949 | if (cd.get_lhs_type ()) |
950 | { |
951 | const svalue *zero |
952 | = mgr->get_or_create_int_cst (type: cd.get_lhs_type (), cst: 0); |
953 | model->set_value (lhs_reg: cd.get_lhs_region (), |
954 | rhs_sval: zero, |
955 | ctxt: cd.get_ctxt ()); |
956 | } |
957 | return true; |
958 | } |
959 | }; |
960 | |
961 | /* Concrete custom_edge_info: a realloc call that succeeds, growing |
962 | the existing buffer without moving it. */ |
963 | class success_no_move : public call_info |
964 | { |
965 | public: |
966 | success_no_move (const call_details &cd) |
967 | : call_info (cd) |
968 | { |
969 | } |
970 | |
971 | label_text get_desc (bool can_colorize) const final override |
972 | { |
973 | return make_label_text (can_colorize, |
974 | fmt: "when %qE succeeds, without moving buffer" , |
975 | get_fndecl ()); |
976 | } |
977 | |
978 | bool update_model (region_model *model, |
979 | const exploded_edge *, |
980 | region_model_context *ctxt) const final override |
981 | { |
982 | /* Update size of buffer and return the ptr unchanged. */ |
983 | const call_details cd (get_call_details (model, ctxt)); |
984 | region_model_manager *mgr = cd.get_manager (); |
985 | const svalue *ptr_sval = cd.get_arg_svalue (idx: 0); |
986 | const svalue *size_sval = cd.get_arg_svalue (idx: 1); |
987 | |
988 | /* We can only grow in place with a non-NULL pointer. */ |
989 | { |
990 | const svalue *null_ptr |
991 | = mgr->get_or_create_int_cst (type: ptr_sval->get_type (), cst: 0); |
992 | if (!model->add_constraint (lhs: ptr_sval, op: NE_EXPR, rhs: null_ptr, |
993 | ctxt: cd.get_ctxt ())) |
994 | return false; |
995 | } |
996 | |
997 | if (const region *buffer_reg = model->deref_rvalue (ptr_sval, NULL_TREE, |
998 | ctxt)) |
999 | if (compat_types_p (src_type: size_sval->get_type (), size_type_node)) |
1000 | model->set_dynamic_extents (reg: buffer_reg, size_in_bytes: size_sval, ctxt); |
1001 | if (cd.get_lhs_region ()) |
1002 | { |
1003 | model->set_value (lhs_reg: cd.get_lhs_region (), rhs_sval: ptr_sval, ctxt: cd.get_ctxt ()); |
1004 | const svalue *zero |
1005 | = mgr->get_or_create_int_cst (type: cd.get_lhs_type (), cst: 0); |
1006 | return model->add_constraint (lhs: ptr_sval, op: NE_EXPR, rhs: zero, ctxt); |
1007 | } |
1008 | else |
1009 | return true; |
1010 | } |
1011 | }; |
1012 | |
1013 | /* Concrete custom_edge_info: a realloc call that succeeds, freeing |
1014 | the existing buffer and moving the content to a freshly allocated |
1015 | buffer. */ |
1016 | class success_with_move : public call_info |
1017 | { |
1018 | public: |
1019 | success_with_move (const call_details &cd) |
1020 | : call_info (cd) |
1021 | { |
1022 | } |
1023 | |
1024 | label_text get_desc (bool can_colorize) const final override |
1025 | { |
1026 | return make_label_text (can_colorize, |
1027 | fmt: "when %qE succeeds, moving buffer" , |
1028 | get_fndecl ()); |
1029 | } |
1030 | bool update_model (region_model *model, |
1031 | const exploded_edge *, |
1032 | region_model_context *ctxt) const final override |
1033 | { |
1034 | const call_details cd (get_call_details (model, ctxt)); |
1035 | region_model_manager *mgr = cd.get_manager (); |
1036 | const svalue *old_ptr_sval = cd.get_arg_svalue (idx: 0); |
1037 | const svalue *new_size_sval = cd.get_arg_svalue (idx: 1); |
1038 | |
1039 | /* Create the new region. */ |
1040 | const region *new_reg |
1041 | = model->get_or_create_region_for_heap_alloc (size_in_bytes: new_size_sval, ctxt); |
1042 | const svalue *new_ptr_sval |
1043 | = mgr->get_ptr_svalue (ptr_type: cd.get_lhs_type (), pointee: new_reg); |
1044 | if (!model->add_constraint (lhs: new_ptr_sval, op: NE_EXPR, rhs: old_ptr_sval, |
1045 | ctxt: cd.get_ctxt ())) |
1046 | return false; |
1047 | |
1048 | if (cd.get_lhs_type ()) |
1049 | cd.maybe_set_lhs (result: new_ptr_sval); |
1050 | |
1051 | if (const region *freed_reg = model->deref_rvalue (ptr_sval: old_ptr_sval, |
1052 | NULL_TREE, ctxt)) |
1053 | { |
1054 | /* Copy the data. */ |
1055 | const svalue *old_size_sval = model->get_dynamic_extents (reg: freed_reg); |
1056 | if (old_size_sval) |
1057 | { |
1058 | const svalue *copied_size_sval |
1059 | = get_copied_size (model, old_size_sval, new_size_sval); |
1060 | const region *copied_old_reg |
1061 | = mgr->get_sized_region (parent: freed_reg, NULL, byte_size_sval: copied_size_sval); |
1062 | const svalue *buffer_content_sval |
1063 | = model->get_store_value (reg: copied_old_reg, ctxt: cd.get_ctxt ()); |
1064 | const region *copied_new_reg |
1065 | = mgr->get_sized_region (parent: new_reg, NULL, byte_size_sval: copied_size_sval); |
1066 | model->set_value (lhs_reg: copied_new_reg, rhs_sval: buffer_content_sval, |
1067 | ctxt: cd.get_ctxt ()); |
1068 | } |
1069 | else |
1070 | { |
1071 | /* We don't know how big the old region was; |
1072 | mark the new region as having been touched to avoid uninit |
1073 | issues. */ |
1074 | model->mark_region_as_unknown (reg: new_reg, uncertainty: cd.get_uncertainty ()); |
1075 | } |
1076 | |
1077 | /* Free the old region, so that pointers to the old buffer become |
1078 | invalid. */ |
1079 | |
1080 | /* If the ptr points to an underlying heap region, delete it, |
1081 | poisoning pointers. */ |
1082 | model->unbind_region_and_descendents (reg: freed_reg, pkind: POISON_KIND_FREED); |
1083 | model->unset_dynamic_extents (reg: freed_reg); |
1084 | } |
1085 | |
1086 | /* Update the sm-state: mark the old_ptr_sval as "freed", |
1087 | and the new_ptr_sval as "nonnull". */ |
1088 | model->on_realloc_with_move (cd, old_ptr_sval, new_ptr_sval); |
1089 | |
1090 | if (cd.get_lhs_type ()) |
1091 | { |
1092 | const svalue *zero |
1093 | = mgr->get_or_create_int_cst (type: cd.get_lhs_type (), cst: 0); |
1094 | return model->add_constraint (lhs: new_ptr_sval, op: NE_EXPR, rhs: zero, |
1095 | ctxt: cd.get_ctxt ()); |
1096 | } |
1097 | else |
1098 | return true; |
1099 | } |
1100 | |
1101 | private: |
1102 | /* Return the lesser of OLD_SIZE_SVAL and NEW_SIZE_SVAL. |
1103 | If unknown, OLD_SIZE_SVAL is returned. */ |
1104 | const svalue *get_copied_size (region_model *model, |
1105 | const svalue *old_size_sval, |
1106 | const svalue *new_size_sval) const |
1107 | { |
1108 | tristate res |
1109 | = model->eval_condition (lhs: old_size_sval, op: GT_EXPR, rhs: new_size_sval); |
1110 | switch (res.get_value ()) |
1111 | { |
1112 | case tristate::TS_TRUE: |
1113 | return new_size_sval; |
1114 | case tristate::TS_FALSE: |
1115 | case tristate::TS_UNKNOWN: |
1116 | return old_size_sval; |
1117 | default: |
1118 | gcc_unreachable (); |
1119 | } |
1120 | } |
1121 | }; |
1122 | |
1123 | /* Body of kf_realloc::impl_call_post. */ |
1124 | |
1125 | if (cd.get_ctxt ()) |
1126 | { |
1127 | cd.get_ctxt ()->bifurcate (info: make_unique<failure> (args: cd)); |
1128 | cd.get_ctxt ()->bifurcate (info: make_unique<success_no_move> (args: cd)); |
1129 | cd.get_ctxt ()->bifurcate (info: make_unique<success_with_move> (args: cd)); |
1130 | cd.get_ctxt ()->terminate_path (); |
1131 | } |
1132 | } |
1133 | |
1134 | /* Handler for "strchr" and "__builtin_strchr". */ |
1135 | |
1136 | class kf_strchr : public builtin_known_function |
1137 | { |
1138 | public: |
1139 | bool matches_call_types_p (const call_details &cd) const final override |
1140 | { |
1141 | return (cd.num_args () == 2 && cd.arg_is_pointer_p (idx: 0)); |
1142 | } |
1143 | void impl_call_pre (const call_details &cd) const final override |
1144 | { |
1145 | cd.check_for_null_terminated_string_arg (arg_idx: 0); |
1146 | } |
1147 | |
1148 | enum built_in_function builtin_code () const final override |
1149 | { |
1150 | return BUILT_IN_STRCHR; |
1151 | } |
1152 | void impl_call_post (const call_details &cd) const final override; |
1153 | }; |
1154 | |
1155 | void |
1156 | kf_strchr::impl_call_post (const call_details &cd) const |
1157 | { |
1158 | class strchr_call_info : public call_info |
1159 | { |
1160 | public: |
1161 | strchr_call_info (const call_details &cd, bool found) |
1162 | : call_info (cd), m_found (found) |
1163 | { |
1164 | } |
1165 | |
1166 | label_text get_desc (bool can_colorize) const final override |
1167 | { |
1168 | if (m_found) |
1169 | return make_label_text (can_colorize, |
1170 | fmt: "when %qE returns non-NULL" , |
1171 | get_fndecl ()); |
1172 | else |
1173 | return make_label_text (can_colorize, |
1174 | fmt: "when %qE returns NULL" , |
1175 | get_fndecl ()); |
1176 | } |
1177 | |
1178 | bool update_model (region_model *model, |
1179 | const exploded_edge *, |
1180 | region_model_context *ctxt) const final override |
1181 | { |
1182 | const call_details cd (get_call_details (model, ctxt)); |
1183 | if (tree lhs_type = cd.get_lhs_type ()) |
1184 | { |
1185 | region_model_manager *mgr = model->get_manager (); |
1186 | const svalue *result; |
1187 | if (m_found) |
1188 | { |
1189 | const svalue *str_sval = cd.get_arg_svalue (idx: 0); |
1190 | const region *str_reg |
1191 | = model->deref_rvalue (ptr_sval: str_sval, ptr_tree: cd.get_arg_tree (idx: 0), |
1192 | ctxt: cd.get_ctxt ()); |
1193 | /* We want str_sval + OFFSET for some unknown OFFSET. |
1194 | Use a conjured_svalue to represent the offset, |
1195 | using the str_reg as the id of the conjured_svalue. */ |
1196 | const svalue *offset |
1197 | = mgr->get_or_create_conjured_svalue (size_type_node, |
1198 | stmt: cd.get_call_stmt (), |
1199 | id_reg: str_reg, |
1200 | p: conjured_purge (model, |
1201 | ctxt)); |
1202 | result = mgr->get_or_create_binop (type: lhs_type, op: POINTER_PLUS_EXPR, |
1203 | arg0: str_sval, arg1: offset); |
1204 | } |
1205 | else |
1206 | result = mgr->get_or_create_int_cst (type: lhs_type, cst: 0); |
1207 | cd.maybe_set_lhs (result); |
1208 | } |
1209 | return true; |
1210 | } |
1211 | private: |
1212 | bool m_found; |
1213 | }; |
1214 | |
1215 | /* Body of kf_strchr::impl_call_post. */ |
1216 | if (cd.get_ctxt ()) |
1217 | { |
1218 | cd.get_ctxt ()->bifurcate (info: make_unique<strchr_call_info> (args: cd, args: false)); |
1219 | cd.get_ctxt ()->bifurcate (info: make_unique<strchr_call_info> (args: cd, args: true)); |
1220 | cd.get_ctxt ()->terminate_path (); |
1221 | } |
1222 | } |
1223 | |
1224 | /* Handler for "sprintf". |
1225 | int sprintf(char *str, const char *format, ...); |
1226 | */ |
1227 | |
1228 | class kf_sprintf : public builtin_known_function |
1229 | { |
1230 | public: |
1231 | bool matches_call_types_p (const call_details &cd) const final override |
1232 | { |
1233 | return (cd.num_args () >= 2 |
1234 | && cd.arg_is_pointer_p (idx: 0) |
1235 | && cd.arg_is_pointer_p (idx: 1)); |
1236 | } |
1237 | |
1238 | enum built_in_function builtin_code () const final override |
1239 | { |
1240 | return BUILT_IN_SPRINTF; |
1241 | } |
1242 | |
1243 | void impl_call_pre (const call_details &cd) const final override |
1244 | { |
1245 | /* For now, merely assume that the destination buffer gets set to a |
1246 | new svalue. */ |
1247 | region_model *model = cd.get_model (); |
1248 | region_model_context *ctxt = cd.get_ctxt (); |
1249 | const svalue *dst_ptr = cd.get_arg_svalue (idx: 0); |
1250 | const region *dst_reg |
1251 | = model->deref_rvalue (ptr_sval: dst_ptr, ptr_tree: cd.get_arg_tree (idx: 0), ctxt); |
1252 | const svalue *content = cd.get_or_create_conjured_svalue (dst_reg); |
1253 | model->set_value (lhs_reg: dst_reg, rhs_sval: content, ctxt); |
1254 | cd.set_any_lhs_with_defaults (); |
1255 | } |
1256 | }; |
1257 | |
1258 | /* Handler for "__builtin_stack_restore". */ |
1259 | |
1260 | class kf_stack_restore : public pure_known_function_with_default_return |
1261 | { |
1262 | public: |
1263 | bool matches_call_types_p (const call_details &) const final override |
1264 | { |
1265 | return true; |
1266 | } |
1267 | |
1268 | /* Currently a no-op. */ |
1269 | }; |
1270 | |
1271 | /* Handler for "__builtin_stack_save". */ |
1272 | |
1273 | class kf_stack_save : public pure_known_function_with_default_return |
1274 | { |
1275 | public: |
1276 | bool matches_call_types_p (const call_details &) const final override |
1277 | { |
1278 | return true; |
1279 | } |
1280 | |
1281 | /* Currently a no-op. */ |
1282 | }; |
1283 | |
1284 | /* Handler for "strcat" and "__builtin_strcat_chk". */ |
1285 | |
1286 | class kf_strcat : public builtin_known_function |
1287 | { |
1288 | public: |
1289 | kf_strcat (unsigned int num_args, bool chk_variant) |
1290 | : m_num_args (num_args), |
1291 | m_chk_variant (chk_variant) {} |
1292 | bool matches_call_types_p (const call_details &cd) const final override |
1293 | { |
1294 | return (cd.num_args () == m_num_args |
1295 | && cd.arg_is_pointer_p (idx: 0) |
1296 | && cd.arg_is_pointer_p (idx: 1)); |
1297 | } |
1298 | |
1299 | enum built_in_function builtin_code () const final override |
1300 | { |
1301 | return m_chk_variant ? BUILT_IN_STRCAT_CHK : BUILT_IN_STRCAT; |
1302 | } |
1303 | |
1304 | void impl_call_pre (const call_details &cd) const final override |
1305 | { |
1306 | region_model *model = cd.get_model (); |
1307 | region_model_manager *mgr = cd.get_manager (); |
1308 | |
1309 | const svalue *dest_sval = cd.get_arg_svalue (idx: 0); |
1310 | const region *dest_reg = model->deref_rvalue (ptr_sval: dest_sval, ptr_tree: cd.get_arg_tree (idx: 0), |
1311 | ctxt: cd.get_ctxt ()); |
1312 | |
1313 | const svalue *dst_strlen_sval |
1314 | = cd.check_for_null_terminated_string_arg (arg_idx: 0, include_terminator: false, out_sval: nullptr); |
1315 | if (!dst_strlen_sval) |
1316 | { |
1317 | if (cd.get_ctxt ()) |
1318 | cd.get_ctxt ()->terminate_path (); |
1319 | return; |
1320 | } |
1321 | |
1322 | const svalue *bytes_to_copy; |
1323 | const svalue *num_src_bytes_read_sval |
1324 | = cd.check_for_null_terminated_string_arg (arg_idx: 1, include_terminator: true, out_sval: &bytes_to_copy); |
1325 | if (!num_src_bytes_read_sval) |
1326 | { |
1327 | if (cd.get_ctxt ()) |
1328 | cd.get_ctxt ()->terminate_path (); |
1329 | return; |
1330 | } |
1331 | |
1332 | cd.maybe_set_lhs (result: dest_sval); |
1333 | cd.complain_about_overlap (arg_idx_a: 0, arg_idx_b: 1, num_bytes_read_sval: num_src_bytes_read_sval); |
1334 | |
1335 | const region *offset_reg |
1336 | = mgr->get_offset_region (parent: dest_reg, NULL_TREE, byte_offset: dst_strlen_sval); |
1337 | model->write_bytes (dest_reg: offset_reg, |
1338 | num_bytes_sval: num_src_bytes_read_sval, |
1339 | sval: bytes_to_copy, |
1340 | ctxt: cd.get_ctxt ()); |
1341 | } |
1342 | |
1343 | private: |
1344 | unsigned int m_num_args; |
1345 | const bool m_chk_variant; |
1346 | }; |
1347 | |
1348 | /* Handler for "strcpy" and "__builtin_strcpy_chk". */ |
1349 | |
1350 | class kf_strcpy : public builtin_known_function |
1351 | { |
1352 | public: |
1353 | kf_strcpy (unsigned int num_args, bool chk_variant) |
1354 | : m_num_args (num_args), |
1355 | m_chk_variant (chk_variant) {} |
1356 | bool matches_call_types_p (const call_details &cd) const final override |
1357 | { |
1358 | return (cd.num_args () == m_num_args |
1359 | && cd.arg_is_pointer_p (idx: 0) |
1360 | && cd.arg_is_pointer_p (idx: 1)); |
1361 | } |
1362 | enum built_in_function builtin_code () const final override |
1363 | { |
1364 | return m_chk_variant ? BUILT_IN_STRCPY_CHK : BUILT_IN_STRCPY; |
1365 | } |
1366 | void impl_call_pre (const call_details &cd) const final override; |
1367 | |
1368 | private: |
1369 | unsigned int m_num_args; |
1370 | const bool m_chk_variant; |
1371 | }; |
1372 | |
1373 | void |
1374 | kf_strcpy::impl_call_pre (const call_details &cd) const |
1375 | { |
1376 | region_model *model = cd.get_model (); |
1377 | region_model_context *ctxt = cd.get_ctxt (); |
1378 | |
1379 | const svalue *dest_sval = cd.get_arg_svalue (idx: 0); |
1380 | const region *dest_reg = model->deref_rvalue (ptr_sval: dest_sval, ptr_tree: cd.get_arg_tree (idx: 0), |
1381 | ctxt); |
1382 | /* strcpy returns the initial param. */ |
1383 | cd.maybe_set_lhs (result: dest_sval); |
1384 | |
1385 | const svalue *bytes_to_copy; |
1386 | if (const svalue *num_bytes_read_sval |
1387 | = cd.check_for_null_terminated_string_arg (arg_idx: 1, include_terminator: true, out_sval: &bytes_to_copy)) |
1388 | { |
1389 | cd.complain_about_overlap (arg_idx_a: 0, arg_idx_b: 1, num_bytes_read_sval); |
1390 | model->write_bytes (dest_reg, num_bytes_sval: num_bytes_read_sval, sval: bytes_to_copy, ctxt); |
1391 | } |
1392 | else |
1393 | { |
1394 | if (cd.get_ctxt ()) |
1395 | cd.get_ctxt ()->terminate_path (); |
1396 | } |
1397 | } |
1398 | |
1399 | /* Handler for "strdup" and "__builtin_strdup". */ |
1400 | |
1401 | class kf_strdup : public builtin_known_function |
1402 | { |
1403 | public: |
1404 | bool matches_call_types_p (const call_details &cd) const final override |
1405 | { |
1406 | return (cd.num_args () == 1 && cd.arg_is_pointer_p (idx: 0)); |
1407 | } |
1408 | enum built_in_function builtin_code () const final override |
1409 | { |
1410 | return BUILT_IN_STRDUP; |
1411 | } |
1412 | void impl_call_pre (const call_details &cd) const final override |
1413 | { |
1414 | region_model *model = cd.get_model (); |
1415 | region_model_context *ctxt = cd.get_ctxt (); |
1416 | region_model_manager *mgr = cd.get_manager (); |
1417 | const svalue *bytes_to_copy; |
1418 | if (const svalue *num_bytes_read_sval |
1419 | = cd.check_for_null_terminated_string_arg (arg_idx: 0, include_terminator: true, out_sval: &bytes_to_copy)) |
1420 | { |
1421 | const region *new_reg |
1422 | = model->get_or_create_region_for_heap_alloc (size_in_bytes: num_bytes_read_sval, |
1423 | ctxt); |
1424 | model->write_bytes (dest_reg: new_reg, num_bytes_sval: num_bytes_read_sval, sval: bytes_to_copy, ctxt); |
1425 | if (cd.get_lhs_type ()) |
1426 | { |
1427 | const svalue *ptr_sval |
1428 | = mgr->get_ptr_svalue (ptr_type: cd.get_lhs_type (), pointee: new_reg); |
1429 | cd.maybe_set_lhs (result: ptr_sval); |
1430 | } |
1431 | } |
1432 | else |
1433 | { |
1434 | if (ctxt) |
1435 | ctxt->terminate_path (); |
1436 | } |
1437 | } |
1438 | }; |
1439 | |
1440 | /* Handler for "strlen" and for "__analyzer_get_strlen". */ |
1441 | |
1442 | class kf_strlen : public builtin_known_function |
1443 | { |
1444 | public: |
1445 | bool matches_call_types_p (const call_details &cd) const final override |
1446 | { |
1447 | return (cd.num_args () == 1 && cd.arg_is_pointer_p (idx: 0)); |
1448 | } |
1449 | enum built_in_function builtin_code () const final override |
1450 | { |
1451 | return BUILT_IN_STRLEN; |
1452 | } |
1453 | |
1454 | void impl_call_pre (const call_details &cd) const final override |
1455 | { |
1456 | if (const svalue *strlen_sval |
1457 | = cd.check_for_null_terminated_string_arg (arg_idx: 0, include_terminator: false, out_sval: nullptr)) |
1458 | if (strlen_sval->get_kind () != SK_UNKNOWN) |
1459 | { |
1460 | cd.maybe_set_lhs (result: strlen_sval); |
1461 | return; |
1462 | } |
1463 | |
1464 | /* Use a conjured svalue. */ |
1465 | cd.set_any_lhs_with_defaults (); |
1466 | } |
1467 | }; |
1468 | |
1469 | /* Factory function, so that kf-analyzer.cc can use this class. */ |
1470 | |
1471 | std::unique_ptr<known_function> |
1472 | make_kf_strlen () |
1473 | { |
1474 | return make_unique<kf_strlen> (); |
1475 | } |
1476 | |
1477 | /* Handler for "strncpy" and "__builtin_strncpy". |
1478 | See e.g. https://en.cppreference.com/w/c/string/byte/strncpy |
1479 | |
1480 | extern char *strncpy (char *dst, const char *src, size_t count); |
1481 | |
1482 | Handle this by splitting into two outcomes: |
1483 | (a) truncated read from "src" of "count" bytes, |
1484 | writing "count" bytes to "dst" |
1485 | (b) read from "src" of up to (and including) the null terminator, |
1486 | where the number of bytes read < "count" bytes, |
1487 | writing those bytes to "dst", and zero-filling the rest, |
1488 | up to "count". */ |
1489 | |
1490 | class kf_strncpy : public builtin_known_function |
1491 | { |
1492 | public: |
1493 | bool matches_call_types_p (const call_details &cd) const final override |
1494 | { |
1495 | return (cd.num_args () == 3 |
1496 | && cd.arg_is_pointer_p (idx: 0) |
1497 | && cd.arg_is_pointer_p (idx: 1) |
1498 | && cd.arg_is_integral_p (idx: 2)); |
1499 | } |
1500 | enum built_in_function builtin_code () const final override |
1501 | { |
1502 | return BUILT_IN_STRNCPY; |
1503 | } |
1504 | void impl_call_post (const call_details &cd) const final override; |
1505 | }; |
1506 | |
1507 | void |
1508 | kf_strncpy::impl_call_post (const call_details &cd) const |
1509 | { |
1510 | class strncpy_call_info : public call_info |
1511 | { |
1512 | public: |
1513 | strncpy_call_info (const call_details &cd, |
1514 | const svalue *num_bytes_with_terminator_sval, |
1515 | bool truncated_read) |
1516 | : call_info (cd), |
1517 | m_num_bytes_with_terminator_sval (num_bytes_with_terminator_sval), |
1518 | m_truncated_read (truncated_read) |
1519 | { |
1520 | } |
1521 | |
1522 | label_text get_desc (bool can_colorize) const final override |
1523 | { |
1524 | if (m_truncated_read) |
1525 | return make_label_text (can_colorize, |
1526 | fmt: "when %qE truncates the source string" , |
1527 | get_fndecl ()); |
1528 | else |
1529 | return make_label_text (can_colorize, |
1530 | fmt: "when %qE copies the full source string" , |
1531 | get_fndecl ()); |
1532 | } |
1533 | |
1534 | bool update_model (region_model *model, |
1535 | const exploded_edge *, |
1536 | region_model_context *ctxt) const final override |
1537 | { |
1538 | const call_details cd (get_call_details (model, ctxt)); |
1539 | |
1540 | const svalue *dest_sval = cd.get_arg_svalue (idx: 0); |
1541 | const region *dest_reg |
1542 | = model->deref_rvalue (ptr_sval: dest_sval, ptr_tree: cd.get_arg_tree (idx: 0), ctxt); |
1543 | |
1544 | const svalue *src_sval = cd.get_arg_svalue (idx: 1); |
1545 | const region *src_reg |
1546 | = model->deref_rvalue (ptr_sval: src_sval, ptr_tree: cd.get_arg_tree (idx: 1), ctxt); |
1547 | |
1548 | const svalue *count_sval = cd.get_arg_svalue (idx: 2); |
1549 | |
1550 | /* strncpy returns the initial param. */ |
1551 | cd.maybe_set_lhs (result: dest_sval); |
1552 | |
1553 | const svalue *num_bytes_read_sval; |
1554 | if (m_truncated_read) |
1555 | { |
1556 | /* Truncated read. */ |
1557 | num_bytes_read_sval = count_sval; |
1558 | |
1559 | if (m_num_bytes_with_terminator_sval) |
1560 | { |
1561 | /* The terminator is after the limit. */ |
1562 | if (!model->add_constraint (lhs: m_num_bytes_with_terminator_sval, |
1563 | op: GT_EXPR, |
1564 | rhs: count_sval, |
1565 | ctxt)) |
1566 | return false; |
1567 | } |
1568 | else |
1569 | { |
1570 | /* We don't know where the terminator is, or if there is one. |
1571 | In theory we know that the first COUNT bytes are non-zero, |
1572 | but we don't have a way to record that constraint. */ |
1573 | } |
1574 | } |
1575 | else |
1576 | { |
1577 | /* Full read of the src string before reaching the limit, |
1578 | so there must be a terminator and it must be at or before |
1579 | the limit. */ |
1580 | if (m_num_bytes_with_terminator_sval) |
1581 | { |
1582 | if (!model->add_constraint (lhs: m_num_bytes_with_terminator_sval, |
1583 | op: LE_EXPR, |
1584 | rhs: count_sval, |
1585 | ctxt)) |
1586 | return false; |
1587 | num_bytes_read_sval = m_num_bytes_with_terminator_sval; |
1588 | |
1589 | /* First, zero-fill the dest buffer. |
1590 | We don't need to do this for the truncation case, as |
1591 | this fully populates the dest buffer. */ |
1592 | const region *sized_dest_reg |
1593 | = model->get_manager ()->get_sized_region (parent: dest_reg, |
1594 | NULL_TREE, |
1595 | byte_size_sval: count_sval); |
1596 | model->zero_fill_region (reg: sized_dest_reg, ctxt); |
1597 | } |
1598 | else |
1599 | { |
1600 | /* Don't analyze this case; the other case will |
1601 | assume a "truncated" read up to the limit. */ |
1602 | return false; |
1603 | } |
1604 | } |
1605 | |
1606 | gcc_assert (num_bytes_read_sval); |
1607 | |
1608 | const svalue *bytes_to_copy |
1609 | = model->read_bytes (src_reg, |
1610 | src_ptr_expr: cd.get_arg_tree (idx: 1), |
1611 | num_bytes_sval: num_bytes_read_sval, |
1612 | ctxt); |
1613 | cd.complain_about_overlap (arg_idx_a: 0, arg_idx_b: 1, num_bytes_read_sval); |
1614 | model->write_bytes (dest_reg, |
1615 | num_bytes_sval: num_bytes_read_sval, |
1616 | sval: bytes_to_copy, |
1617 | ctxt); |
1618 | |
1619 | return true; |
1620 | } |
1621 | private: |
1622 | /* (strlen + 1) of the source string if it has a terminator, |
1623 | or NULL for the case where UB would happen before |
1624 | finding any terminator. */ |
1625 | const svalue *m_num_bytes_with_terminator_sval; |
1626 | |
1627 | /* true: if this is the outcome where the limit was reached before |
1628 | the null terminator |
1629 | false: if the null terminator was reached before the limit. */ |
1630 | bool m_truncated_read; |
1631 | }; |
1632 | |
1633 | /* Body of kf_strncpy::impl_call_post. */ |
1634 | if (cd.get_ctxt ()) |
1635 | { |
1636 | /* First, scan for a null terminator as if there were no limit, |
1637 | with a null ctxt so no errors are reported. */ |
1638 | const region_model *model = cd.get_model (); |
1639 | const svalue *ptr_arg_sval = cd.get_arg_svalue (idx: 1); |
1640 | const region *buf_reg |
1641 | = model->deref_rvalue (ptr_sval: ptr_arg_sval, ptr_tree: cd.get_arg_tree (idx: 1), ctxt: nullptr); |
1642 | const svalue *num_bytes_with_terminator_sval |
1643 | = model->scan_for_null_terminator (reg: buf_reg, |
1644 | expr: cd.get_arg_tree (idx: 1), |
1645 | out_sval: nullptr, |
1646 | ctxt: nullptr); |
1647 | cd.get_ctxt ()->bifurcate |
1648 | (info: make_unique<strncpy_call_info> (args: cd, args&: num_bytes_with_terminator_sval, |
1649 | args: false)); |
1650 | cd.get_ctxt ()->bifurcate |
1651 | (info: make_unique<strncpy_call_info> (args: cd, args&: num_bytes_with_terminator_sval, |
1652 | args: true)); |
1653 | cd.get_ctxt ()->terminate_path (); |
1654 | } |
1655 | }; |
1656 | |
1657 | /* Handler for "strndup" and "__builtin_strndup". */ |
1658 | |
1659 | class kf_strndup : public builtin_known_function |
1660 | { |
1661 | public: |
1662 | bool matches_call_types_p (const call_details &cd) const final override |
1663 | { |
1664 | return (cd.num_args () == 2 && cd.arg_is_pointer_p (idx: 0)); |
1665 | } |
1666 | enum built_in_function builtin_code () const final override |
1667 | { |
1668 | return BUILT_IN_STRNDUP; |
1669 | } |
1670 | void impl_call_pre (const call_details &cd) const final override |
1671 | { |
1672 | region_model *model = cd.get_model (); |
1673 | region_model_manager *mgr = cd.get_manager (); |
1674 | /* Ideally we'd get the size here, and simulate copying the bytes. */ |
1675 | const region *new_reg |
1676 | = model->get_or_create_region_for_heap_alloc (NULL, ctxt: cd.get_ctxt ()); |
1677 | model->mark_region_as_unknown (reg: new_reg, NULL); |
1678 | if (cd.get_lhs_type ()) |
1679 | { |
1680 | const svalue *ptr_sval |
1681 | = mgr->get_ptr_svalue (ptr_type: cd.get_lhs_type (), pointee: new_reg); |
1682 | cd.maybe_set_lhs (result: ptr_sval); |
1683 | } |
1684 | } |
1685 | }; |
1686 | |
1687 | /* Handler for "strstr" and "__builtin_strstr". |
1688 | extern char *strstr (const char* str, const char* substr); |
1689 | See e.g. https://en.cppreference.com/w/c/string/byte/strstr */ |
1690 | |
1691 | class kf_strstr : public builtin_known_function |
1692 | { |
1693 | public: |
1694 | bool matches_call_types_p (const call_details &cd) const final override |
1695 | { |
1696 | return (cd.num_args () == 2 |
1697 | && cd.arg_is_pointer_p (idx: 0) |
1698 | && cd.arg_is_pointer_p (idx: 1)); |
1699 | } |
1700 | enum built_in_function builtin_code () const final override |
1701 | { |
1702 | return BUILT_IN_STRSTR; |
1703 | } |
1704 | void impl_call_pre (const call_details &cd) const final override |
1705 | { |
1706 | cd.check_for_null_terminated_string_arg (arg_idx: 0); |
1707 | cd.check_for_null_terminated_string_arg (arg_idx: 1); |
1708 | } |
1709 | void impl_call_post (const call_details &cd) const final override; |
1710 | }; |
1711 | |
1712 | void |
1713 | kf_strstr::impl_call_post (const call_details &cd) const |
1714 | { |
1715 | class strstr_call_info : public call_info |
1716 | { |
1717 | public: |
1718 | strstr_call_info (const call_details &cd, bool found) |
1719 | : call_info (cd), m_found (found) |
1720 | { |
1721 | } |
1722 | |
1723 | label_text get_desc (bool can_colorize) const final override |
1724 | { |
1725 | if (m_found) |
1726 | return make_label_text (can_colorize, |
1727 | fmt: "when %qE returns non-NULL" , |
1728 | get_fndecl ()); |
1729 | else |
1730 | return make_label_text (can_colorize, |
1731 | fmt: "when %qE returns NULL" , |
1732 | get_fndecl ()); |
1733 | } |
1734 | |
1735 | bool update_model (region_model *model, |
1736 | const exploded_edge *, |
1737 | region_model_context *ctxt) const final override |
1738 | { |
1739 | const call_details cd (get_call_details (model, ctxt)); |
1740 | if (tree lhs_type = cd.get_lhs_type ()) |
1741 | { |
1742 | region_model_manager *mgr = model->get_manager (); |
1743 | const svalue *result; |
1744 | if (m_found) |
1745 | { |
1746 | const svalue *str_sval = cd.get_arg_svalue (idx: 0); |
1747 | const region *str_reg |
1748 | = model->deref_rvalue (ptr_sval: str_sval, ptr_tree: cd.get_arg_tree (idx: 0), |
1749 | ctxt: cd.get_ctxt ()); |
1750 | /* We want str_sval + OFFSET for some unknown OFFSET. |
1751 | Use a conjured_svalue to represent the offset, |
1752 | using the str_reg as the id of the conjured_svalue. */ |
1753 | const svalue *offset |
1754 | = mgr->get_or_create_conjured_svalue (size_type_node, |
1755 | stmt: cd.get_call_stmt (), |
1756 | id_reg: str_reg, |
1757 | p: conjured_purge (model, |
1758 | ctxt)); |
1759 | result = mgr->get_or_create_binop (type: lhs_type, op: POINTER_PLUS_EXPR, |
1760 | arg0: str_sval, arg1: offset); |
1761 | } |
1762 | else |
1763 | result = mgr->get_or_create_int_cst (type: lhs_type, cst: 0); |
1764 | cd.maybe_set_lhs (result); |
1765 | } |
1766 | return true; |
1767 | } |
1768 | private: |
1769 | bool m_found; |
1770 | }; |
1771 | |
1772 | /* Body of kf_strstr::impl_call_post. */ |
1773 | if (cd.get_ctxt ()) |
1774 | { |
1775 | cd.get_ctxt ()->bifurcate (info: make_unique<strstr_call_info> (args: cd, args: false)); |
1776 | cd.get_ctxt ()->bifurcate (info: make_unique<strstr_call_info> (args: cd, args: true)); |
1777 | cd.get_ctxt ()->terminate_path (); |
1778 | } |
1779 | } |
1780 | |
1781 | /* Handle calls to "strtok". |
1782 | See e.g. |
1783 | https://en.cppreference.com/w/c/string/byte/strtok |
1784 | https://man7.org/linux/man-pages/man3/strtok.3.html */ |
1785 | |
1786 | class kf_strtok : public known_function |
1787 | { |
1788 | public: |
1789 | class undefined_behavior : public undefined_function_behavior |
1790 | { |
1791 | public: |
1792 | undefined_behavior (const call_details &cd) |
1793 | : undefined_function_behavior (cd) |
1794 | { |
1795 | } |
1796 | int get_controlling_option () const final override |
1797 | { |
1798 | return OPT_Wanalyzer_undefined_behavior_strtok; |
1799 | } |
1800 | |
1801 | bool emit (diagnostic_emission_context &ctxt) final override |
1802 | { |
1803 | /* CWE-476: NULL Pointer Dereference. */ |
1804 | ctxt.add_cwe (cwe: 476); |
1805 | if (ctxt.warn ("calling %qD for first time with NULL as argument 1" |
1806 | " has undefined behavior" , |
1807 | get_callee_fndecl ())) |
1808 | { |
1809 | inform (ctxt.get_location (), |
1810 | "some implementations of %qD may crash on such input" , |
1811 | get_callee_fndecl ()); |
1812 | return true; |
1813 | } |
1814 | return false; |
1815 | } |
1816 | |
1817 | label_text describe_final_event (const evdesc::final_event &ev) |
1818 | final override |
1819 | { |
1820 | return ev.formatted_print |
1821 | (fmt: "calling %qD for first time with NULL as argument 1" |
1822 | " has undefined behavior" , |
1823 | get_callee_fndecl ()); |
1824 | } |
1825 | }; |
1826 | |
1827 | /* An outcome of a "strtok" call. |
1828 | We have a four-way bifurcation of the analysis via the |
1829 | 4 combinations of two flags: |
1830 | - m_nonnull_str covers whether the "str" param was null or non-null |
1831 | - m_found covers whether the result is null or non-null |
1832 | */ |
1833 | class strtok_call_info : public call_info |
1834 | { |
1835 | public: |
1836 | strtok_call_info (const call_details &cd, |
1837 | const private_region &private_reg, |
1838 | bool nonnull_str, |
1839 | bool found) |
1840 | : call_info (cd), |
1841 | m_private_reg (private_reg), |
1842 | m_nonnull_str (nonnull_str), |
1843 | m_found (found) |
1844 | { |
1845 | } |
1846 | |
1847 | label_text get_desc (bool can_colorize) const final override |
1848 | { |
1849 | if (m_nonnull_str) |
1850 | { |
1851 | if (m_found) |
1852 | return make_label_text |
1853 | (can_colorize, |
1854 | fmt: "when %qE on non-NULL string returns non-NULL" , |
1855 | get_fndecl ()); |
1856 | else |
1857 | return make_label_text |
1858 | (can_colorize, |
1859 | fmt: "when %qE on non-NULL string returns NULL" , |
1860 | get_fndecl ()); |
1861 | } |
1862 | else |
1863 | { |
1864 | if (m_found) |
1865 | return make_label_text |
1866 | (can_colorize, |
1867 | fmt: "when %qE with NULL string (using prior) returns non-NULL" , |
1868 | get_fndecl ()); |
1869 | else |
1870 | return make_label_text |
1871 | (can_colorize, |
1872 | fmt: "when %qE with NULL string (using prior) returns NULL" , |
1873 | get_fndecl ()); |
1874 | } |
1875 | } |
1876 | |
1877 | bool update_model (region_model *model, |
1878 | const exploded_edge *, |
1879 | region_model_context *ctxt) const final override |
1880 | { |
1881 | region_model_manager *mgr = model->get_manager (); |
1882 | const call_details cd (get_call_details (model, ctxt)); |
1883 | const svalue *str_sval = cd.get_arg_svalue (idx: 0); |
1884 | /* const svalue *delim_sval = cd.get_arg_svalue (1); */ |
1885 | |
1886 | cd.check_for_null_terminated_string_arg (arg_idx: 1); |
1887 | /* We check that either arg 0 or the private region is null |
1888 | terminated below. */ |
1889 | |
1890 | const svalue *null_ptr_sval |
1891 | = mgr->get_or_create_null_ptr (pointer_type: cd.get_arg_type (idx: 0));; |
1892 | if (!model->add_constraint (lhs: str_sval, |
1893 | op: m_nonnull_str ? NE_EXPR : EQ_EXPR, |
1894 | rhs: null_ptr_sval, |
1895 | ctxt: cd.get_ctxt ())) |
1896 | return false; |
1897 | |
1898 | if (m_nonnull_str) |
1899 | { |
1900 | /* Update internal buffer. */ |
1901 | model->set_value (lhs_reg: &m_private_reg, |
1902 | rhs_sval: mgr->get_or_create_unmergeable (arg: str_sval), |
1903 | ctxt); |
1904 | } |
1905 | else |
1906 | { |
1907 | /* Read from internal buffer. */ |
1908 | str_sval = model->get_store_value (reg: &m_private_reg, ctxt); |
1909 | |
1910 | /* The initial value of the private region is NULL when we're |
1911 | on a path from main. */ |
1912 | if (const initial_svalue *initial_sval |
1913 | = str_sval->dyn_cast_initial_svalue ()) |
1914 | if (initial_sval->get_region () == &m_private_reg |
1915 | && model->called_from_main_p ()) |
1916 | { |
1917 | /* Implementations of strtok do not necessarily check for NULL |
1918 | here, and may crash; see PR analyzer/107573. |
1919 | Warn for this, if we were definitely passed NULL. */ |
1920 | if (cd.get_arg_svalue (idx: 0)->all_zeroes_p ()) |
1921 | { |
1922 | if (ctxt) |
1923 | ctxt->warn (d: ::make_unique<undefined_behavior> (args: cd)); |
1924 | } |
1925 | |
1926 | /* Assume that "str" was actually non-null; terminate |
1927 | this path. */ |
1928 | return false; |
1929 | } |
1930 | |
1931 | /* Now assume str_sval is non-null. */ |
1932 | if (!model->add_constraint (lhs: str_sval, |
1933 | op: NE_EXPR, |
1934 | rhs: null_ptr_sval, |
1935 | ctxt: cd.get_ctxt ())) |
1936 | return false; |
1937 | } |
1938 | |
1939 | const region *buf_reg = model->deref_rvalue (ptr_sval: str_sval, NULL_TREE, ctxt); |
1940 | model->scan_for_null_terminator (reg: buf_reg, |
1941 | NULL_TREE, |
1942 | out_sval: nullptr, |
1943 | ctxt); |
1944 | |
1945 | if (m_found) |
1946 | { |
1947 | const region *str_reg |
1948 | = model->deref_rvalue (ptr_sval: str_sval, ptr_tree: cd.get_arg_tree (idx: 0), |
1949 | ctxt: cd.get_ctxt ()); |
1950 | /* We want to figure out the start and nul terminator |
1951 | for the token. |
1952 | For each, we want str_sval + OFFSET for some unknown OFFSET. |
1953 | Use a conjured_svalue to represent the offset, |
1954 | using the str_reg as the id of the conjured_svalue. */ |
1955 | const svalue *start_offset |
1956 | = mgr->get_or_create_conjured_svalue (size_type_node, |
1957 | stmt: cd.get_call_stmt (), |
1958 | id_reg: str_reg, |
1959 | p: conjured_purge (model, |
1960 | ctxt), |
1961 | idx: 0); |
1962 | const svalue *nul_offset |
1963 | = mgr->get_or_create_conjured_svalue (size_type_node, |
1964 | stmt: cd.get_call_stmt (), |
1965 | id_reg: str_reg, |
1966 | p: conjured_purge (model, |
1967 | ctxt), |
1968 | idx: 1); |
1969 | |
1970 | tree char_ptr_type = build_pointer_type (char_type_node); |
1971 | const svalue *result |
1972 | = mgr->get_or_create_binop (type: char_ptr_type, op: POINTER_PLUS_EXPR, |
1973 | arg0: str_sval, arg1: start_offset); |
1974 | cd.maybe_set_lhs (result); |
1975 | |
1976 | /* nul_offset + 1; the offset to use for the next call. */ |
1977 | const svalue *next_offset |
1978 | = mgr->get_or_create_binop (size_type_node, op: PLUS_EXPR, |
1979 | arg0: nul_offset, |
1980 | arg1: mgr->get_or_create_int_cst |
1981 | (char_type_node, cst: 1)); |
1982 | |
1983 | /* Write '\0' to str_sval[nul_offset]. */ |
1984 | const svalue *ptr_to_term |
1985 | = mgr->get_or_create_binop (type: char_ptr_type, op: POINTER_PLUS_EXPR, |
1986 | arg0: str_sval, arg1: nul_offset); |
1987 | const region *terminator_reg |
1988 | = model->deref_rvalue (ptr_sval: ptr_to_term, NULL_TREE, ctxt: cd.get_ctxt ()); |
1989 | model->set_value (lhs_reg: terminator_reg, |
1990 | rhs_sval: mgr->get_or_create_unmergeable |
1991 | (arg: mgr->get_or_create_int_cst (char_type_node, |
1992 | cst: 0)), |
1993 | ctxt: cd.get_ctxt ()); |
1994 | |
1995 | /* Update saved ptr to be at [nul_offset + 1]. */ |
1996 | const svalue *ptr_to_next |
1997 | = mgr->get_or_create_binop (type: cd.get_lhs_type (), op: POINTER_PLUS_EXPR, |
1998 | arg0: str_sval, arg1: next_offset); |
1999 | model->set_value (lhs_reg: &m_private_reg, rhs_sval: ptr_to_next, ctxt); |
2000 | } |
2001 | else |
2002 | if (tree lhs_type = cd.get_lhs_type ()) |
2003 | { |
2004 | const svalue *result |
2005 | = mgr->get_or_create_int_cst (type: lhs_type, cst: 0); |
2006 | cd.maybe_set_lhs (result); |
2007 | } |
2008 | return true; |
2009 | } |
2010 | private: |
2011 | const private_region &m_private_reg; |
2012 | bool m_nonnull_str; |
2013 | bool m_found; |
2014 | }; // class strtok_call_info |
2015 | |
2016 | kf_strtok (region_model_manager &mgr) |
2017 | : m_private_reg (mgr.alloc_symbol_id (), |
2018 | mgr.get_root_region (), |
2019 | get_region_type (), |
2020 | "strtok buffer" ) |
2021 | { |
2022 | } |
2023 | |
2024 | bool matches_call_types_p (const call_details &cd) const final override |
2025 | { |
2026 | return (cd.num_args () == 2 |
2027 | && POINTER_TYPE_P (cd.get_arg_type (0)) |
2028 | && POINTER_TYPE_P (cd.get_arg_type (1))); |
2029 | } |
2030 | |
2031 | void impl_call_post (const call_details &cd) const final override |
2032 | { |
2033 | if (cd.get_ctxt ()) |
2034 | { |
2035 | /* Four-way bifurcation, based on whether: |
2036 | - the str is non-null |
2037 | - the result is non-null |
2038 | Typically the str is either null or non-null at a particular site, |
2039 | so hopefully this will generally just lead to two out-edges. */ |
2040 | cd.get_ctxt ()->bifurcate |
2041 | (info: make_unique<strtok_call_info> (args: cd, args: m_private_reg, args: false, args: false)); |
2042 | cd.get_ctxt ()->bifurcate |
2043 | (info: make_unique<strtok_call_info> (args: cd, args: m_private_reg, args: false, args: true)); |
2044 | cd.get_ctxt ()->bifurcate |
2045 | (info: make_unique<strtok_call_info> (args: cd, args: m_private_reg, args: true, args: false)); |
2046 | cd.get_ctxt ()->bifurcate |
2047 | (info: make_unique<strtok_call_info> (args: cd, args: m_private_reg, args: true, args: true)); |
2048 | cd.get_ctxt ()->terminate_path (); |
2049 | } |
2050 | } |
2051 | |
2052 | private: |
2053 | static tree get_region_type () |
2054 | { |
2055 | return build_pointer_type (char_type_node); |
2056 | } |
2057 | const private_region m_private_reg; |
2058 | }; |
2059 | |
2060 | class kf_ubsan_bounds : public internal_known_function |
2061 | { |
2062 | /* Empty. */ |
2063 | }; |
2064 | |
2065 | /* Handle calls to functions referenced by |
2066 | __attribute__((malloc(FOO))). */ |
2067 | |
2068 | void |
2069 | region_model::impl_deallocation_call (const call_details &cd) |
2070 | { |
2071 | kf_free kf; |
2072 | kf.impl_call_post (cd); |
2073 | } |
2074 | |
2075 | static void |
2076 | register_atomic_builtins (known_function_manager &kfm) |
2077 | { |
2078 | kfm.add (name: BUILT_IN_ATOMIC_EXCHANGE, kf: make_unique<kf_atomic_exchange> ()); |
2079 | kfm.add (name: BUILT_IN_ATOMIC_EXCHANGE_N, kf: make_unique<kf_atomic_exchange_n> ()); |
2080 | kfm.add (name: BUILT_IN_ATOMIC_EXCHANGE_1, kf: make_unique<kf_atomic_exchange_n> ()); |
2081 | kfm.add (name: BUILT_IN_ATOMIC_EXCHANGE_2, kf: make_unique<kf_atomic_exchange_n> ()); |
2082 | kfm.add (name: BUILT_IN_ATOMIC_EXCHANGE_4, kf: make_unique<kf_atomic_exchange_n> ()); |
2083 | kfm.add (name: BUILT_IN_ATOMIC_EXCHANGE_8, kf: make_unique<kf_atomic_exchange_n> ()); |
2084 | kfm.add (name: BUILT_IN_ATOMIC_EXCHANGE_16, kf: make_unique<kf_atomic_exchange_n> ()); |
2085 | kfm.add (name: BUILT_IN_ATOMIC_LOAD, kf: make_unique<kf_atomic_load> ()); |
2086 | kfm.add (name: BUILT_IN_ATOMIC_LOAD_N, kf: make_unique<kf_atomic_load_n> ()); |
2087 | kfm.add (name: BUILT_IN_ATOMIC_LOAD_1, kf: make_unique<kf_atomic_load_n> ()); |
2088 | kfm.add (name: BUILT_IN_ATOMIC_LOAD_2, kf: make_unique<kf_atomic_load_n> ()); |
2089 | kfm.add (name: BUILT_IN_ATOMIC_LOAD_4, kf: make_unique<kf_atomic_load_n> ()); |
2090 | kfm.add (name: BUILT_IN_ATOMIC_LOAD_8, kf: make_unique<kf_atomic_load_n> ()); |
2091 | kfm.add (name: BUILT_IN_ATOMIC_LOAD_16, kf: make_unique<kf_atomic_load_n> ()); |
2092 | kfm.add (name: BUILT_IN_ATOMIC_STORE, kf: make_unique<kf_atomic_store> ()); |
2093 | kfm.add (name: BUILT_IN_ATOMIC_STORE_N, kf: make_unique<kf_atomic_store_n> ()); |
2094 | kfm.add (name: BUILT_IN_ATOMIC_STORE_1, kf: make_unique<kf_atomic_store_n> ()); |
2095 | kfm.add (name: BUILT_IN_ATOMIC_STORE_2, kf: make_unique<kf_atomic_store_n> ()); |
2096 | kfm.add (name: BUILT_IN_ATOMIC_STORE_4, kf: make_unique<kf_atomic_store_n> ()); |
2097 | kfm.add (name: BUILT_IN_ATOMIC_STORE_8, kf: make_unique<kf_atomic_store_n> ()); |
2098 | kfm.add (name: BUILT_IN_ATOMIC_STORE_16, kf: make_unique<kf_atomic_store_n> ()); |
2099 | kfm.add (name: BUILT_IN_ATOMIC_ADD_FETCH_1, |
2100 | kf: make_unique<kf_atomic_op_fetch> (args: PLUS_EXPR)); |
2101 | kfm.add (name: BUILT_IN_ATOMIC_ADD_FETCH_2, |
2102 | kf: make_unique<kf_atomic_op_fetch> (args: PLUS_EXPR)); |
2103 | kfm.add (name: BUILT_IN_ATOMIC_ADD_FETCH_4, |
2104 | kf: make_unique<kf_atomic_op_fetch> (args: PLUS_EXPR)); |
2105 | kfm.add (name: BUILT_IN_ATOMIC_ADD_FETCH_8, |
2106 | kf: make_unique<kf_atomic_op_fetch> (args: PLUS_EXPR)); |
2107 | kfm.add (name: BUILT_IN_ATOMIC_ADD_FETCH_16, |
2108 | kf: make_unique<kf_atomic_op_fetch> (args: PLUS_EXPR)); |
2109 | kfm.add (name: BUILT_IN_ATOMIC_SUB_FETCH_1, |
2110 | kf: make_unique<kf_atomic_op_fetch> (args: MINUS_EXPR)); |
2111 | kfm.add (name: BUILT_IN_ATOMIC_SUB_FETCH_2, |
2112 | kf: make_unique<kf_atomic_op_fetch> (args: MINUS_EXPR)); |
2113 | kfm.add (name: BUILT_IN_ATOMIC_SUB_FETCH_4, |
2114 | kf: make_unique<kf_atomic_op_fetch> (args: MINUS_EXPR)); |
2115 | kfm.add (name: BUILT_IN_ATOMIC_SUB_FETCH_8, |
2116 | kf: make_unique<kf_atomic_op_fetch> (args: MINUS_EXPR)); |
2117 | kfm.add (name: BUILT_IN_ATOMIC_SUB_FETCH_16, |
2118 | kf: make_unique<kf_atomic_op_fetch> (args: MINUS_EXPR)); |
2119 | kfm.add (name: BUILT_IN_ATOMIC_AND_FETCH_1, |
2120 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_AND_EXPR)); |
2121 | kfm.add (name: BUILT_IN_ATOMIC_AND_FETCH_2, |
2122 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_AND_EXPR)); |
2123 | kfm.add (name: BUILT_IN_ATOMIC_AND_FETCH_4, |
2124 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_AND_EXPR)); |
2125 | kfm.add (name: BUILT_IN_ATOMIC_AND_FETCH_8, |
2126 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_AND_EXPR)); |
2127 | kfm.add (name: BUILT_IN_ATOMIC_AND_FETCH_16, |
2128 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_AND_EXPR)); |
2129 | kfm.add (name: BUILT_IN_ATOMIC_XOR_FETCH_1, |
2130 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_XOR_EXPR)); |
2131 | kfm.add (name: BUILT_IN_ATOMIC_XOR_FETCH_2, |
2132 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_XOR_EXPR)); |
2133 | kfm.add (name: BUILT_IN_ATOMIC_XOR_FETCH_4, |
2134 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_XOR_EXPR)); |
2135 | kfm.add (name: BUILT_IN_ATOMIC_XOR_FETCH_8, |
2136 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_XOR_EXPR)); |
2137 | kfm.add (name: BUILT_IN_ATOMIC_XOR_FETCH_16, |
2138 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_XOR_EXPR)); |
2139 | kfm.add (name: BUILT_IN_ATOMIC_OR_FETCH_1, |
2140 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_IOR_EXPR)); |
2141 | kfm.add (name: BUILT_IN_ATOMIC_OR_FETCH_2, |
2142 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_IOR_EXPR)); |
2143 | kfm.add (name: BUILT_IN_ATOMIC_OR_FETCH_4, |
2144 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_IOR_EXPR)); |
2145 | kfm.add (name: BUILT_IN_ATOMIC_OR_FETCH_8, |
2146 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_IOR_EXPR)); |
2147 | kfm.add (name: BUILT_IN_ATOMIC_OR_FETCH_16, |
2148 | kf: make_unique<kf_atomic_op_fetch> (args: BIT_IOR_EXPR)); |
2149 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_ADD_1, |
2150 | kf: make_unique<kf_atomic_fetch_op> (args: PLUS_EXPR)); |
2151 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_ADD_2, |
2152 | kf: make_unique<kf_atomic_fetch_op> (args: PLUS_EXPR)); |
2153 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_ADD_4, |
2154 | kf: make_unique<kf_atomic_fetch_op> (args: PLUS_EXPR)); |
2155 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_ADD_8, |
2156 | kf: make_unique<kf_atomic_fetch_op> (args: PLUS_EXPR)); |
2157 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_ADD_16, |
2158 | kf: make_unique<kf_atomic_fetch_op> (args: PLUS_EXPR)); |
2159 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_SUB_1, |
2160 | kf: make_unique<kf_atomic_fetch_op> (args: MINUS_EXPR)); |
2161 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_SUB_2, |
2162 | kf: make_unique<kf_atomic_fetch_op> (args: MINUS_EXPR)); |
2163 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_SUB_4, |
2164 | kf: make_unique<kf_atomic_fetch_op> (args: MINUS_EXPR)); |
2165 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_SUB_8, |
2166 | kf: make_unique<kf_atomic_fetch_op> (args: MINUS_EXPR)); |
2167 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_SUB_16, |
2168 | kf: make_unique<kf_atomic_fetch_op> (args: MINUS_EXPR)); |
2169 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_AND_1, |
2170 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_AND_EXPR)); |
2171 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_AND_2, |
2172 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_AND_EXPR)); |
2173 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_AND_4, |
2174 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_AND_EXPR)); |
2175 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_AND_8, |
2176 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_AND_EXPR)); |
2177 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_AND_16, |
2178 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_AND_EXPR)); |
2179 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_XOR_1, |
2180 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_XOR_EXPR)); |
2181 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_XOR_2, |
2182 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_XOR_EXPR)); |
2183 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_XOR_4, |
2184 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_XOR_EXPR)); |
2185 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_XOR_8, |
2186 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_XOR_EXPR)); |
2187 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_XOR_16, |
2188 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_XOR_EXPR)); |
2189 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_OR_1, |
2190 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_IOR_EXPR)); |
2191 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_OR_2, |
2192 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_IOR_EXPR)); |
2193 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_OR_4, |
2194 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_IOR_EXPR)); |
2195 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_OR_8, |
2196 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_IOR_EXPR)); |
2197 | kfm.add (name: BUILT_IN_ATOMIC_FETCH_OR_16, |
2198 | kf: make_unique<kf_atomic_fetch_op> (args: BIT_IOR_EXPR)); |
2199 | } |
2200 | |
2201 | /* Handle calls to the various __builtin___ubsan_handle_*. |
2202 | These can return, but continuing after such a return |
2203 | isn't likely to be interesting to the user of the analyzer. |
2204 | Hence we terminate the analysis path at one of these calls. */ |
2205 | |
2206 | class kf_ubsan_handler : public internal_known_function |
2207 | { |
2208 | void impl_call_post (const call_details &cd) const final override |
2209 | { |
2210 | if (cd.get_ctxt ()) |
2211 | cd.get_ctxt ()->terminate_path (); |
2212 | } |
2213 | }; |
2214 | |
2215 | static void |
2216 | register_sanitizer_builtins (known_function_manager &kfm) |
2217 | { |
2218 | kfm.add (name: BUILT_IN_UBSAN_HANDLE_NONNULL_ARG, |
2219 | kf: make_unique<kf_ubsan_handler> ()); |
2220 | } |
2221 | |
2222 | /* Populate KFM with instances of known functions supported by the core of the |
2223 | analyzer (as opposed to plugins). */ |
2224 | |
2225 | void |
2226 | register_known_functions (known_function_manager &kfm, |
2227 | region_model_manager &rmm) |
2228 | { |
2229 | /* Debugging/test support functions, all with a "__analyzer_" prefix. */ |
2230 | register_known_analyzer_functions (kfm); |
2231 | |
2232 | /* Internal fns the analyzer has known_functions for. */ |
2233 | { |
2234 | kfm.add (ifn: IFN_BUILTIN_EXPECT, kf: make_unique<kf_expect> ()); |
2235 | kfm.add (ifn: IFN_UBSAN_BOUNDS, kf: make_unique<kf_ubsan_bounds> ()); |
2236 | } |
2237 | |
2238 | /* GCC built-ins that do not correspond to a function |
2239 | in the standard library. */ |
2240 | { |
2241 | kfm.add (name: BUILT_IN_EXPECT, kf: make_unique<kf_expect> ()); |
2242 | kfm.add (name: BUILT_IN_EXPECT_WITH_PROBABILITY, kf: make_unique<kf_expect> ()); |
2243 | kfm.add (name: BUILT_IN_ALLOCA_WITH_ALIGN, kf: make_unique<kf_alloca> ()); |
2244 | kfm.add (name: BUILT_IN_STACK_RESTORE, kf: make_unique<kf_stack_restore> ()); |
2245 | kfm.add (name: BUILT_IN_STACK_SAVE, kf: make_unique<kf_stack_save> ()); |
2246 | |
2247 | register_atomic_builtins (kfm); |
2248 | register_sanitizer_builtins (kfm); |
2249 | register_varargs_builtins (kfm); |
2250 | } |
2251 | |
2252 | /* Known builtins and C standard library functions |
2253 | the analyzer has known functions for. */ |
2254 | { |
2255 | kfm.add (name: "alloca" , kf: make_unique<kf_alloca> ()); |
2256 | kfm.add (name: "__builtin_alloca" , kf: make_unique<kf_alloca> ()); |
2257 | kfm.add (name: "calloc" , kf: make_unique<kf_calloc> ()); |
2258 | kfm.add (name: "__builtin_calloc" , kf: make_unique<kf_calloc> ()); |
2259 | kfm.add (name: "free" , kf: make_unique<kf_free> ()); |
2260 | kfm.add (name: "__builtin_free" , kf: make_unique<kf_free> ()); |
2261 | kfm.add (name: "malloc" , kf: make_unique<kf_malloc> ()); |
2262 | kfm.add (name: "__builtin_malloc" , kf: make_unique<kf_malloc> ()); |
2263 | kfm.add (name: "memcpy" , |
2264 | kf: make_unique<kf_memcpy_memmove> (args: kf_memcpy_memmove::KF_MEMCPY)); |
2265 | kfm.add (name: "__builtin_memcpy" , |
2266 | kf: make_unique<kf_memcpy_memmove> (args: kf_memcpy_memmove::KF_MEMCPY)); |
2267 | kfm.add (name: "__memcpy_chk" , kf: make_unique<kf_memcpy_memmove> |
2268 | (args: kf_memcpy_memmove::KF_MEMCPY_CHK)); |
2269 | kfm.add (name: "__builtin___memcpy_chk" , kf: make_unique<kf_memcpy_memmove> |
2270 | (args: kf_memcpy_memmove::KF_MEMCPY_CHK)); |
2271 | kfm.add (name: "memmove" , |
2272 | kf: make_unique<kf_memcpy_memmove> (args: kf_memcpy_memmove::KF_MEMMOVE)); |
2273 | kfm.add (name: "__builtin_memmove" , |
2274 | kf: make_unique<kf_memcpy_memmove> (args: kf_memcpy_memmove::KF_MEMMOVE)); |
2275 | kfm.add (name: "__memmove_chk" , kf: make_unique<kf_memcpy_memmove> |
2276 | (args: kf_memcpy_memmove::KF_MEMMOVE_CHK)); |
2277 | kfm.add (name: "__builtin___memmove_chk" , kf: make_unique<kf_memcpy_memmove> |
2278 | (args: kf_memcpy_memmove::KF_MEMMOVE_CHK)); |
2279 | kfm.add (name: "memset" , kf: make_unique<kf_memset> (args: false)); |
2280 | kfm.add (name: "__builtin_memset" , kf: make_unique<kf_memset> (args: false)); |
2281 | kfm.add (name: "__memset_chk" , kf: make_unique<kf_memset> (args: true)); |
2282 | kfm.add (name: "__builtin___memset_chk" , kf: make_unique<kf_memset> (args: true)); |
2283 | kfm.add (name: "realloc" , kf: make_unique<kf_realloc> ()); |
2284 | kfm.add (name: "__builtin_realloc" , kf: make_unique<kf_realloc> ()); |
2285 | kfm.add (name: "sprintf" , kf: make_unique<kf_sprintf> ()); |
2286 | kfm.add (name: "__builtin_sprintf" , kf: make_unique<kf_sprintf> ()); |
2287 | kfm.add (name: "strchr" , kf: make_unique<kf_strchr> ()); |
2288 | kfm.add (name: "__builtin_strchr" , kf: make_unique<kf_strchr> ()); |
2289 | kfm.add (name: "strcpy" , kf: make_unique<kf_strcpy> (args: 2, args: false)); |
2290 | kfm.add (name: "__builtin_strcpy" , kf: make_unique<kf_strcpy> (args: 2, args: false)); |
2291 | kfm.add (name: "__strcpy_chk" , kf: make_unique<kf_strcpy> (args: 3, args: true)); |
2292 | kfm.add (name: "__builtin___strcpy_chk" , kf: make_unique<kf_strcpy> (args: 3, args: true)); |
2293 | kfm.add (name: "strcat" , kf: make_unique<kf_strcat> (args: 2, args: false)); |
2294 | kfm.add (name: "__builtin_strcat" , kf: make_unique<kf_strcat> (args: 2, args: false)); |
2295 | kfm.add (name: "__strcat_chk" , kf: make_unique<kf_strcat> (args: 3, args: true)); |
2296 | kfm.add (name: "__builtin___strcat_chk" , kf: make_unique<kf_strcat> (args: 3, args: true)); |
2297 | kfm.add (name: "strdup" , kf: make_unique<kf_strdup> ()); |
2298 | kfm.add (name: "__builtin_strdup" , kf: make_unique<kf_strdup> ()); |
2299 | kfm.add (name: "strncpy" , kf: make_unique<kf_strncpy> ()); |
2300 | kfm.add (name: "__builtin_strncpy" , kf: make_unique<kf_strncpy> ()); |
2301 | kfm.add (name: "strndup" , kf: make_unique<kf_strndup> ()); |
2302 | kfm.add (name: "__builtin_strndup" , kf: make_unique<kf_strndup> ()); |
2303 | kfm.add (name: "strlen" , kf: make_unique<kf_strlen> ()); |
2304 | kfm.add (name: "__builtin_strlen" , kf: make_unique<kf_strlen> ()); |
2305 | kfm.add (name: "strstr" , kf: make_unique<kf_strstr> ()); |
2306 | kfm.add (name: "__builtin_strstr" , kf: make_unique<kf_strstr> ()); |
2307 | |
2308 | register_atomic_builtins (kfm); |
2309 | register_varargs_builtins (kfm); |
2310 | } |
2311 | |
2312 | /* Known POSIX functions, and some non-standard extensions. */ |
2313 | { |
2314 | kfm.add (name: "fopen" , kf: make_unique<kf_fopen> ()); |
2315 | kfm.add (name: "putenv" , kf: make_unique<kf_putenv> ()); |
2316 | kfm.add (name: "strtok" , kf: make_unique<kf_strtok> (args&: rmm)); |
2317 | |
2318 | register_known_fd_functions (kfm); |
2319 | register_known_file_functions (kfm); |
2320 | } |
2321 | |
2322 | /* glibc functions. */ |
2323 | { |
2324 | kfm.add (name: "__errno_location" , kf: make_unique<kf_errno_location> ()); |
2325 | kfm.add (name: "error" , kf: make_unique<kf_error> (args: 3)); |
2326 | kfm.add (name: "error_at_line" , kf: make_unique<kf_error> (args: 5)); |
2327 | } |
2328 | |
2329 | /* Other implementations of C standard library. */ |
2330 | { |
2331 | /* According to PR 107807 comment #2, Solaris implements "errno" |
2332 | like this: |
2333 | extern int *___errno(void) __attribute__((__const__)); |
2334 | #define errno (*(___errno())) |
2335 | and macOS like this: |
2336 | extern int * __error(void); |
2337 | #define errno (*__error()) |
2338 | and similarly __errno for newlib. |
2339 | Add these as synonyms for "__errno_location". */ |
2340 | kfm.add (name: "___errno" , kf: make_unique<kf_errno_location> ()); |
2341 | kfm.add (name: "__error" , kf: make_unique<kf_errno_location> ()); |
2342 | kfm.add (name: "__errno" , kf: make_unique<kf_errno_location> ()); |
2343 | } |
2344 | |
2345 | /* Language-specific support functions. */ |
2346 | register_known_functions_lang_cp (kfm); |
2347 | } |
2348 | |
2349 | } // namespace ana |
2350 | |
2351 | #endif /* #if ENABLE_ANALYZER */ |
2352 | |