mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-22 03:47:02 -05:00
377 lines
11 KiB
C++
377 lines
11 KiB
C++
/* Handling for the known behavior of various functions specific to C++.
|
|
Copyright (C) 2020-2026 Free Software Foundation, Inc.
|
|
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
|
|
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
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "analyzer/common.h"
|
|
|
|
#include "diagnostic.h"
|
|
|
|
#include "analyzer/analyzer-logging.h"
|
|
#include "analyzer/region-model.h"
|
|
#include "analyzer/call-details.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)
|
|
{
|
|
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
|
|
check_any_preconditions (const call_details &cd) const final override
|
|
{
|
|
region_model_context *ctxt = cd.get_ctxt ();
|
|
if (!ctxt)
|
|
return;
|
|
region_model *model = cd.get_model ();
|
|
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))
|
|
{
|
|
if (const region *sized_reg = get_sized_region_for_placement_new (cd))
|
|
model->check_region_for_write (sized_reg,
|
|
nullptr,
|
|
ctxt);
|
|
}
|
|
}
|
|
|
|
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 (is_placement_new_p (call))
|
|
{
|
|
const region *ptr_reg = cd.deref_ptr_arg (1);
|
|
if (ptr_reg && cd.get_lhs_type ())
|
|
if (const region *sized_reg = get_sized_region_for_placement_new (cd))
|
|
{
|
|
const svalue *ptr_sval
|
|
= mgr->get_ptr_svalue (cd.get_lhs_type (), sized_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);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const region *
|
|
get_sized_region_for_placement_new (const call_details &cd) const
|
|
{
|
|
const region *ptr_reg = cd.deref_ptr_arg (1);
|
|
if (ptr_reg && cd.get_lhs_type ())
|
|
{
|
|
region_model_manager *mgr = cd.get_manager ();
|
|
const svalue *num_bytes_sval = cd.get_arg_svalue (0);
|
|
return mgr->get_sized_region (ptr_reg,
|
|
cd.get_lhs_type (),
|
|
num_bytes_sval);
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
class kf_cxa_allocate_exception : 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);
|
|
}
|
|
|
|
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 ();
|
|
|
|
/* Create a heap allocated region. */
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
class kf_cxa_begin_catch : public known_function
|
|
{
|
|
public:
|
|
bool matches_call_types_p (const call_details &cd) const final override
|
|
{
|
|
return (cd.num_args () == 1
|
|
&& POINTER_TYPE_P (cd.get_arg_type (0)));
|
|
}
|
|
|
|
void impl_call_pre (const call_details &cd) const final override
|
|
{
|
|
region_model *model = cd.get_model ();
|
|
|
|
auto node = model->pop_thrown_exception ();
|
|
model->push_caught_exception (node);
|
|
cd.maybe_set_lhs (node.m_exception_sval);
|
|
}
|
|
};
|
|
|
|
class kf_cxa_end_catch : public known_function
|
|
{
|
|
public:
|
|
bool matches_call_types_p (const call_details &cd) const final override
|
|
{
|
|
return cd.num_args () == 0;
|
|
}
|
|
|
|
void impl_call_pre (const call_details &cd) const final override
|
|
{
|
|
region_model *model = cd.get_model ();
|
|
model->pop_caught_exception ();
|
|
}
|
|
};
|
|
|
|
/* A subclass of pending_diagnostic for complaining about an exception
|
|
of an unexpected type being thrown (due to a call to
|
|
__cxa_call_unexpected).
|
|
See https://en.cppreference.com/w/cpp/language/except_spec */
|
|
|
|
class throw_of_unexpected_type
|
|
: public pending_diagnostic_subclass<throw_of_unexpected_type>
|
|
{
|
|
public:
|
|
throw_of_unexpected_type (tree exception_type,
|
|
tree thrown_from_fndecl)
|
|
: m_exception_type (exception_type),
|
|
m_thrown_from_fndecl (thrown_from_fndecl)
|
|
{
|
|
gcc_assert (m_exception_type);
|
|
gcc_assert (m_thrown_from_fndecl);
|
|
}
|
|
|
|
const char *get_kind () const final override
|
|
{
|
|
return "throw_of_unexpected_type";
|
|
}
|
|
|
|
bool operator== (const throw_of_unexpected_type &other) const
|
|
{
|
|
return (m_exception_type == other.m_exception_type
|
|
&& m_thrown_from_fndecl == other.m_thrown_from_fndecl);
|
|
}
|
|
|
|
int get_controlling_option () const final override
|
|
{
|
|
return OPT_Wanalyzer_throw_of_unexpected_type;
|
|
}
|
|
|
|
bool emit (diagnostic_emission_context &ctxt) final override
|
|
{
|
|
auto_diagnostic_group d;
|
|
|
|
bool warned
|
|
= ctxt.warn ("throwing exception of unexpected type %qT from %qE",
|
|
m_exception_type, m_thrown_from_fndecl);
|
|
if (warned)
|
|
{
|
|
inform (DECL_SOURCE_LOCATION (m_thrown_from_fndecl),
|
|
"%qE declared here", m_thrown_from_fndecl);
|
|
// TODO: show specified types?
|
|
}
|
|
return warned;
|
|
}
|
|
|
|
bool
|
|
describe_final_event (pretty_printer &pp,
|
|
const evdesc::final_event &) final override
|
|
{
|
|
pp_printf (&pp,
|
|
"exception of unexpected type %qT thrown from %qE",
|
|
m_exception_type, m_thrown_from_fndecl);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
tree m_exception_type;
|
|
tree m_thrown_from_fndecl;
|
|
};
|
|
|
|
/* See https://en.cppreference.com/w/cpp/language/except_spec */
|
|
|
|
class kf_cxa_call_unexpected : public known_function
|
|
{
|
|
public:
|
|
bool matches_call_types_p (const call_details &cd) const final override
|
|
{
|
|
return (cd.num_args () == 1
|
|
&& POINTER_TYPE_P (cd.get_arg_type (0)));
|
|
}
|
|
|
|
void impl_call_pre (const call_details &cd) const final override
|
|
{
|
|
if (region_model_context *ctxt = cd.get_ctxt ())
|
|
{
|
|
region_model *model = cd.get_model ();
|
|
tree thrown_from_fndecl = model->get_current_function ()->decl;
|
|
/* We must have a thrown exception. */
|
|
auto eh_node = model->get_current_thrown_exception ();
|
|
gcc_assert (eh_node);
|
|
tree exception_type = eh_node->maybe_get_type ();
|
|
ctxt->warn
|
|
(std::make_unique<throw_of_unexpected_type> (exception_type,
|
|
thrown_from_fndecl));
|
|
ctxt->terminate_path ();
|
|
}
|
|
}
|
|
};
|
|
|
|
/* Populate KFM with instances of known functions relating to C++. */
|
|
|
|
void
|
|
register_known_functions_lang_cp (known_function_manager &kfm)
|
|
{
|
|
kfm.add ("operator new", std::make_unique<kf_operator_new> ());
|
|
kfm.add ("operator new []", std::make_unique<kf_operator_new> ());
|
|
kfm.add ("operator delete", std::make_unique<kf_operator_delete> ());
|
|
kfm.add ("operator delete []", std::make_unique<kf_operator_delete> ());
|
|
|
|
/* Functions mentioned in "Itanium C++ ABI: Exception Handling"'s
|
|
"Level II: C++ ABI"
|
|
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#cxx-abi */
|
|
kfm.add ("__cxa_allocate_exception",
|
|
std::make_unique<kf_cxa_allocate_exception> ());
|
|
// We treat __cxa_throw and __cxa_rethrow as special cases
|
|
kfm.add ("__cxa_begin_catch", std::make_unique<kf_cxa_begin_catch> ());
|
|
kfm.add ("__cxa_end_catch", std::make_unique<kf_cxa_end_catch> ());
|
|
kfm.add ("__cxa_call_unexpected",
|
|
std::make_unique<kf_cxa_call_unexpected> ());
|
|
}
|
|
|
|
} // namespace ana
|
|
|
|
#endif /* #if ENABLE_ANALYZER */
|