/* Handling for the known behavior of various functions specific to C++. Copyright (C) 2020-2024 Free Software Foundation, Inc. Contributed by David Malcolm . This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "config.h" #define INCLUDE_MEMORY #include "system.h" #include "coretypes.h" #include "tree.h" #include "function.h" #include "basic-block.h" #include "gimple.h" #include "analyzer/analyzer.h" #include "analyzer/analyzer-logging.h" #include "diagnostic.h" #include "analyzer/region-model.h" #include "analyzer/call-details.h" #include "make-unique.h" #if ENABLE_ANALYZER /* Return true if CALL is a non-allocating operator new or operator new [] that contains no user-defined args, i.e. having any signature of: - void* operator new (std::size_t count, void* ptr); - void* operator new[] (std::size_t count, void* ptr); See https://en.cppreference.com/w/cpp/memory/new/operator_new. */ bool is_placement_new_p (const gcall *call) { gcc_assert (call); tree fndecl = gimple_call_fndecl (call); if (!fndecl || TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE) /* Give up on overloaded operator new. */ return false; if (!is_named_call_p (fndecl, "operator new", call, 2) && !is_named_call_p (fndecl, "operator new []", call, 2)) return false; /* We must distinguish between an allocating non-throwing new and a non-allocating new. The former might have one of the following signatures : void* operator new (std::size_t count, const std::nothrow_t& tag); void* operator new[] (std::size_t count, const std::nothrow_t& tag); Whereas a placement new would take a pointer. */ tree arg1_type = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (fndecl))); return TREE_CODE (TREE_VALUE (arg1_type)) == POINTER_TYPE; } namespace ana { /* Implementations of specific functions. */ /* Handler for "operator new" and "operator new []". */ class kf_operator_new : public known_function { public: bool matches_call_types_p (const call_details &cd) const final override { return (cd.num_args () == 1 && cd.arg_is_size_p (0)) || (cd.num_args () == 2 && cd.arg_is_size_p (0) && POINTER_TYPE_P (cd.get_arg_type (1))); } void impl_call_pre (const call_details &cd) const final override { region_model *model = cd.get_model (); region_model_manager *mgr = cd.get_manager (); const svalue *size_sval = cd.get_arg_svalue (0); region_model_context *ctxt = cd.get_ctxt (); const gcall *call = cd.get_call_stmt (); /* If the call was actually a placement new, check that accessing the buffer lhs is placed into does not result in out-of-bounds. */ if (is_placement_new_p (call)) { const region *ptr_reg = cd.deref_ptr_arg (1); if (ptr_reg && cd.get_lhs_type ()) { const svalue *num_bytes_sval = cd.get_arg_svalue (0); const region *sized_new_reg = mgr->get_sized_region (ptr_reg, cd.get_lhs_type (), num_bytes_sval); model->check_region_for_write (sized_new_reg, nullptr, ctxt); const svalue *ptr_sval = mgr->get_ptr_svalue (cd.get_lhs_type (), sized_new_reg); cd.maybe_set_lhs (ptr_sval); } } /* If the call is an allocating new, then create a heap allocated region. */ else { const region *new_reg = model->get_or_create_region_for_heap_alloc (size_sval, ctxt); if (cd.get_lhs_type ()) { const svalue *ptr_sval = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); cd.maybe_set_lhs (ptr_sval); } } } void impl_call_post (const call_details &cd) const final override { region_model *model = cd.get_model (); region_model_manager *mgr = cd.get_manager (); tree callee_fndecl = cd.get_fndecl_for_call (); region_model_context *ctxt = cd.get_ctxt (); /* If the call is guaranteed to return nonnull then add a nonnull constraint to the allocated region. */ if (!TREE_NOTHROW (callee_fndecl) && flag_exceptions) { const svalue *null_sval = mgr->get_or_create_null_ptr (cd.get_lhs_type ()); const svalue *result = model->get_store_value (cd.get_lhs_region (), ctxt); model->add_constraint (result, NE_EXPR, null_sval, ctxt); } } }; /* Handler for "operator delete" and for "operator delete []", both the sized and unsized variants (2 arguments and 1 argument respectively). */ class kf_operator_delete : public known_function { public: bool matches_call_types_p (const call_details &cd) const final override { return cd.num_args () == 1 or cd.num_args () == 2; } void impl_call_post (const call_details &cd) const final override { region_model *model = cd.get_model (); const svalue *ptr_sval = cd.get_arg_svalue (0); if (const region *freed_reg = ptr_sval->maybe_get_region ()) { /* If the ptr points to an underlying heap region, delete it, poisoning pointers. */ model->unbind_region_and_descendents (freed_reg, POISON_KIND_DELETED); } } }; /* Populate KFM with instances of known functions relating to C++. */ void register_known_functions_lang_cp (known_function_manager &kfm) { kfm.add ("operator new", make_unique ()); kfm.add ("operator new []", make_unique ()); kfm.add ("operator delete", make_unique ()); kfm.add ("operator delete []", make_unique ()); } } // namespace ana #endif /* #if ENABLE_ANALYZER */