Line data Source code
1 : /* Handling for the known behavior of various functions specific to C++.
2 : Copyright (C) 2020-2026 Free Software Foundation, Inc.
3 : Contributed by David Malcolm <dmalcolm@redhat.com>.
4 :
5 : This file is part of GCC.
6 :
7 : GCC is free software; you can redistribute it and/or modify it
8 : under the terms of the GNU General Public License as published by
9 : the Free Software Foundation; either version 3, or (at your option)
10 : any later version.
11 :
12 : GCC is distributed in the hope that it will be useful, but
13 : WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with GCC; see the file COPYING3. If not see
19 : <http://www.gnu.org/licenses/>. */
20 :
21 : #include "analyzer/common.h"
22 :
23 : #include "diagnostic.h"
24 :
25 : #include "analyzer/analyzer-logging.h"
26 : #include "analyzer/region-model.h"
27 : #include "analyzer/call-details.h"
28 :
29 : #if ENABLE_ANALYZER
30 :
31 : /* Return true if CALL is a non-allocating operator new or operator new []
32 : that contains no user-defined args, i.e. having any signature of:
33 :
34 : - void* operator new (std::size_t count, void* ptr);
35 : - void* operator new[] (std::size_t count, void* ptr);
36 :
37 : See https://en.cppreference.com/w/cpp/memory/new/operator_new. */
38 :
39 40880 : bool is_placement_new_p (const gcall &call)
40 : {
41 40880 : tree fndecl = gimple_call_fndecl (&call);
42 :
43 40880 : if (!fndecl || TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
44 : /* Give up on overloaded operator new. */
45 : return false;
46 :
47 40524 : if (!is_named_call_p (fndecl, "operator new", call, 2)
48 40524 : && !is_named_call_p (fndecl, "operator new []", call, 2))
49 : return false;
50 :
51 : /* We must distinguish between an allocating non-throwing new
52 : and a non-allocating new.
53 :
54 : The former might have one of the following signatures :
55 : void* operator new (std::size_t count, const std::nothrow_t& tag);
56 : void* operator new[] (std::size_t count, const std::nothrow_t& tag);
57 : Whereas a placement new would take a pointer. */
58 1065 : tree arg1_type = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (fndecl)));
59 1065 : return TREE_CODE (TREE_VALUE (arg1_type)) == POINTER_TYPE;
60 : }
61 :
62 : namespace ana {
63 :
64 : /* Implementations of specific functions. */
65 :
66 : /* Handler for "operator new" and "operator new []". */
67 :
68 6754 : class kf_operator_new : public known_function
69 : {
70 : public:
71 3348 : bool matches_call_types_p (const call_details &cd) const final override
72 : {
73 3348 : return (cd.num_args () == 1
74 1221 : && cd.arg_is_size_p (0))
75 3348 : || (cd.num_args () == 2
76 2127 : && cd.arg_is_size_p (0)
77 2127 : && POINTER_TYPE_P (cd.get_arg_type (1)));
78 : }
79 :
80 : void
81 228 : check_any_preconditions (const call_details &cd) const final override
82 : {
83 228 : region_model_context *ctxt = cd.get_ctxt ();
84 228 : if (!ctxt)
85 : return;
86 228 : region_model *model = cd.get_model ();
87 228 : const gcall &call = cd.get_call_stmt ();
88 :
89 : /* If the call was actually a placement new, check that accessing
90 : the buffer lhs is placed into does not result in out-of-bounds. */
91 228 : if (is_placement_new_p (call))
92 : {
93 69 : if (const region *sized_reg = get_sized_region_for_placement_new (cd))
94 57 : model->check_region_for_write (sized_reg,
95 : nullptr,
96 : ctxt);
97 : }
98 : }
99 :
100 1236 : void impl_call_pre (const call_details &cd) const final override
101 : {
102 1236 : region_model *model = cd.get_model ();
103 1236 : region_model_manager *mgr = cd.get_manager ();
104 1236 : const svalue *size_sval = cd.get_arg_svalue (0);
105 1236 : region_model_context *ctxt = cd.get_ctxt ();
106 1236 : const gcall &call = cd.get_call_stmt ();
107 :
108 1236 : if (is_placement_new_p (call))
109 : {
110 12 : const region *ptr_reg = cd.deref_ptr_arg (1);
111 12 : if (ptr_reg && cd.get_lhs_type ())
112 0 : if (const region *sized_reg = get_sized_region_for_placement_new (cd))
113 : {
114 0 : const svalue *ptr_sval
115 0 : = mgr->get_ptr_svalue (cd.get_lhs_type (), sized_reg);
116 0 : cd.maybe_set_lhs (ptr_sval);
117 : }
118 : }
119 : /* If the call is an allocating new, then create a heap allocated
120 : region. */
121 : else
122 : {
123 1224 : const region *new_reg
124 1224 : = model->get_or_create_region_for_heap_alloc (size_sval, ctxt);
125 1224 : if (cd.get_lhs_type ())
126 : {
127 1224 : const svalue *ptr_sval
128 1224 : = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
129 1224 : cd.maybe_set_lhs (ptr_sval);
130 : }
131 : }
132 1236 : }
133 :
134 1236 : void impl_call_post (const call_details &cd) const final override
135 : {
136 1236 : region_model *model = cd.get_model ();
137 1236 : region_model_manager *mgr = cd.get_manager ();
138 1236 : tree callee_fndecl = cd.get_fndecl_for_call ();
139 1236 : region_model_context *ctxt = cd.get_ctxt ();
140 :
141 : /* If the call is guaranteed to return nonnull
142 : then add a nonnull constraint to the allocated region. */
143 1236 : if (!TREE_NOTHROW (callee_fndecl)
144 348 : && flag_exceptions
145 1494 : && cd.get_lhs_type ())
146 : {
147 246 : const svalue *null_sval
148 246 : = mgr->get_or_create_null_ptr (cd.get_lhs_type ());
149 246 : const svalue *result
150 246 : = model->get_store_value (cd.get_lhs_region (), ctxt);
151 246 : model->add_constraint (result, NE_EXPR, null_sval, ctxt);
152 : }
153 1236 : }
154 :
155 : private:
156 : const region *
157 69 : get_sized_region_for_placement_new (const call_details &cd) const
158 : {
159 69 : const region *ptr_reg = cd.deref_ptr_arg (1);
160 69 : if (ptr_reg && cd.get_lhs_type ())
161 : {
162 57 : region_model_manager *mgr = cd.get_manager ();
163 57 : const svalue *num_bytes_sval = cd.get_arg_svalue (0);
164 57 : return mgr->get_sized_region (ptr_reg,
165 : cd.get_lhs_type (),
166 57 : num_bytes_sval);
167 : }
168 : return nullptr;
169 : }
170 : };
171 :
172 : /* Handler for "operator delete" and for "operator delete []",
173 : both the sized and unsized variants
174 : (2 arguments and 1 argument respectively). */
175 :
176 6754 : class kf_operator_delete : public known_function
177 : {
178 : public:
179 1338 : bool matches_call_types_p (const call_details &cd) const final override
180 : {
181 1338 : return cd.num_args () == 1 or cd.num_args () == 2;
182 : }
183 :
184 242 : void impl_call_post (const call_details &cd) const final override
185 : {
186 242 : region_model *model = cd.get_model ();
187 242 : const svalue *ptr_sval = cd.get_arg_svalue (0);
188 242 : if (const region *freed_reg = ptr_sval->maybe_get_region ())
189 : {
190 : /* If the ptr points to an underlying heap region, delete it,
191 : poisoning pointers. */
192 195 : model->unbind_region_and_descendents (freed_reg,
193 : poison_kind::deleted);
194 : }
195 242 : }
196 :
197 : };
198 :
199 3377 : class kf_cxa_allocate_exception : public known_function
200 : {
201 : public:
202 874 : bool matches_call_types_p (const call_details &cd) const final override
203 : {
204 874 : return cd.num_args () == 1 && cd.arg_is_size_p (0);
205 : }
206 :
207 172 : void impl_call_pre (const call_details &cd) const final override
208 : {
209 172 : region_model *model = cd.get_model ();
210 172 : region_model_manager *mgr = cd.get_manager ();
211 172 : const svalue *size_sval = cd.get_arg_svalue (0);
212 172 : region_model_context *ctxt = cd.get_ctxt ();
213 :
214 : /* Create a heap allocated region. */
215 172 : const region *new_reg
216 172 : = model->get_or_create_region_for_heap_alloc (size_sval, ctxt);
217 172 : if (cd.get_lhs_type ())
218 : {
219 172 : const svalue *ptr_sval
220 172 : = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
221 172 : cd.maybe_set_lhs (ptr_sval);
222 : }
223 172 : }
224 : };
225 :
226 3377 : class kf_cxa_begin_catch : public known_function
227 : {
228 : public:
229 979 : bool matches_call_types_p (const call_details &cd) const final override
230 : {
231 979 : return (cd.num_args () == 1
232 979 : && POINTER_TYPE_P (cd.get_arg_type (0)));
233 : }
234 :
235 187 : void impl_call_pre (const call_details &cd) const final override
236 : {
237 187 : region_model *model = cd.get_model ();
238 :
239 187 : auto node = model->pop_thrown_exception ();
240 187 : model->push_caught_exception (node);
241 187 : cd.maybe_set_lhs (node.m_exception_sval);
242 187 : }
243 : };
244 :
245 3377 : class kf_cxa_end_catch : public known_function
246 : {
247 : public:
248 1057 : bool matches_call_types_p (const call_details &cd) const final override
249 : {
250 1057 : return cd.num_args () == 0;
251 : }
252 :
253 166 : void impl_call_pre (const call_details &cd) const final override
254 : {
255 166 : region_model *model = cd.get_model ();
256 166 : model->pop_caught_exception ();
257 166 : }
258 : };
259 :
260 : /* A subclass of pending_diagnostic for complaining about an exception
261 : of an unexpected type being thrown (due to a call to
262 : __cxa_call_unexpected).
263 : See https://en.cppreference.com/w/cpp/language/except_spec */
264 :
265 : class throw_of_unexpected_type
266 : : public pending_diagnostic_subclass<throw_of_unexpected_type>
267 : {
268 : public:
269 3 : throw_of_unexpected_type (tree exception_type,
270 : tree thrown_from_fndecl)
271 3 : : m_exception_type (exception_type),
272 3 : m_thrown_from_fndecl (thrown_from_fndecl)
273 : {
274 3 : gcc_assert (m_exception_type);
275 3 : gcc_assert (m_thrown_from_fndecl);
276 3 : }
277 :
278 24 : const char *get_kind () const final override
279 : {
280 24 : return "throw_of_unexpected_type";
281 : }
282 :
283 3 : bool operator== (const throw_of_unexpected_type &other) const
284 : {
285 3 : return (m_exception_type == other.m_exception_type
286 3 : && m_thrown_from_fndecl == other.m_thrown_from_fndecl);
287 : }
288 :
289 6 : int get_controlling_option () const final override
290 : {
291 6 : return OPT_Wanalyzer_throw_of_unexpected_type;
292 : }
293 :
294 3 : bool emit (diagnostic_emission_context &ctxt) final override
295 : {
296 3 : auto_diagnostic_group d;
297 :
298 3 : bool warned
299 3 : = ctxt.warn ("throwing exception of unexpected type %qT from %qE",
300 : m_exception_type, m_thrown_from_fndecl);
301 3 : if (warned)
302 : {
303 3 : inform (DECL_SOURCE_LOCATION (m_thrown_from_fndecl),
304 : "%qE declared here", m_thrown_from_fndecl);
305 : // TODO: show specified types?
306 : }
307 6 : return warned;
308 3 : }
309 :
310 : bool
311 6 : describe_final_event (pretty_printer &pp,
312 : const evdesc::final_event &) final override
313 : {
314 6 : pp_printf (&pp,
315 : "exception of unexpected type %qT thrown from %qE",
316 : m_exception_type, m_thrown_from_fndecl);
317 6 : return true;
318 : }
319 :
320 : private:
321 : tree m_exception_type;
322 : tree m_thrown_from_fndecl;
323 : };
324 :
325 : /* See https://en.cppreference.com/w/cpp/language/except_spec */
326 :
327 3377 : class kf_cxa_call_unexpected : public known_function
328 : {
329 : public:
330 18 : bool matches_call_types_p (const call_details &cd) const final override
331 : {
332 18 : return (cd.num_args () == 1
333 18 : && POINTER_TYPE_P (cd.get_arg_type (0)));
334 : }
335 :
336 3 : void impl_call_pre (const call_details &cd) const final override
337 : {
338 3 : if (region_model_context *ctxt = cd.get_ctxt ())
339 : {
340 3 : region_model *model = cd.get_model ();
341 3 : tree thrown_from_fndecl = model->get_current_function ()->decl;
342 : /* We must have a thrown exception. */
343 3 : auto eh_node = model->get_current_thrown_exception ();
344 0 : gcc_assert (eh_node);
345 3 : tree exception_type = eh_node->maybe_get_type ();
346 3 : ctxt->warn
347 3 : (std::make_unique<throw_of_unexpected_type> (exception_type,
348 : thrown_from_fndecl));
349 3 : ctxt->terminate_path ();
350 : }
351 3 : }
352 : };
353 :
354 : /* Populate KFM with instances of known functions relating to C++. */
355 :
356 : void
357 3377 : register_known_functions_lang_cp (known_function_manager &kfm)
358 : {
359 3377 : kfm.add ("operator new", std::make_unique<kf_operator_new> ());
360 3377 : kfm.add ("operator new []", std::make_unique<kf_operator_new> ());
361 3377 : kfm.add ("operator delete", std::make_unique<kf_operator_delete> ());
362 3377 : kfm.add ("operator delete []", std::make_unique<kf_operator_delete> ());
363 :
364 : /* Functions mentioned in "Itanium C++ ABI: Exception Handling"'s
365 : "Level II: C++ ABI"
366 : https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#cxx-abi */
367 3377 : kfm.add ("__cxa_allocate_exception",
368 3377 : std::make_unique<kf_cxa_allocate_exception> ());
369 : // We treat __cxa_throw and __cxa_rethrow as special cases
370 3377 : kfm.add ("__cxa_begin_catch", std::make_unique<kf_cxa_begin_catch> ());
371 3377 : kfm.add ("__cxa_end_catch", std::make_unique<kf_cxa_end_catch> ());
372 3377 : kfm.add ("__cxa_call_unexpected",
373 3377 : std::make_unique<kf_cxa_call_unexpected> ());
374 3377 : }
375 :
376 : } // namespace ana
377 :
378 : #endif /* #if ENABLE_ANALYZER */
|