aboutsummaryrefslogtreecommitdiff
path: root/gcc/analyzer
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2023-08-21 21:13:19 -0400
committerDavid Malcolm <dmalcolm@redhat.com>2023-08-21 21:13:19 -0400
commitfe97f09a0caeff2a22cc41b26bf08692bff8686d (patch)
treecfbe083ec82f0a543007c4f67109d0f28326643d /gcc/analyzer
parent1e7b0a5d7a45dc5932992d36e1375582d61269e4 (diff)
downloadgcc-fe97f09a0caeff2a22cc41b26bf08692bff8686d.zip
gcc-fe97f09a0caeff2a22cc41b26bf08692bff8686d.tar.gz
gcc-fe97f09a0caeff2a22cc41b26bf08692bff8686d.tar.bz2
analyzer: replace -Wanalyzer-unterminated-string with scan_for_null_terminator [PR105899]
In r14-3169-g325f9e88802daa I added check_for_null_terminated_string_arg to -fanalyzer, calling it in various places, with a sole check for unterminated string constants, adding -Wanalyzer-unterminated-string for this case. This patch adds region_model::scan_for_null_terminator, which simulates scanning memory for a zero byte, complaining about uninitiliazed bytes and out-of-range accesses seen before any zero byte is seen. This more flexible approach catches the issues we saw before with -Wanalyzer-unterminated-string, and also catches uninitialized runs of bytes, and I believe will be a better way to build checking of C string operations in the analyzer. Given that the patch makes -Wanalyzer-unterminated-string redundant and that this option was only in trunk for 10 days and has no known users, the patch simply removes the option without a compatibility fallback. The patch uses custom events and notes to provide context on where the issues are coming from. For example, given: null-terminated-strings-1.c: In function ‘test_partially_initialized’: null-terminated-strings-1.c:71:3: warning: use of uninitialized value ‘buf[1]’ [CWE-457] [-Wanalyzer-use-of-uninitialized-value] 71 | __analyzer_get_strlen (buf); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ ‘test_partially_initialized’: events 1-3 | | 69 | char buf[16]; | | ^~~ | | | | | (1) region created on stack here | 70 | buf[0] = 'a'; | 71 | __analyzer_get_strlen (buf); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (2) while looking for null terminator for argument 1 (‘&buf’) of ‘__analyzer_get_strlen’... | | (3) use of uninitialized value ‘buf[1]’ here | analyzer-decls.h:59:22: note: argument 1 of ‘__analyzer_get_strlen’ must be a pointer to a null-terminated string 59 | extern __SIZE_TYPE__ __analyzer_get_strlen (const char *ptr); | ^~~~~~~~~~~~~~~~~~~~~ gcc/analyzer/ChangeLog: PR analyzer/105899 * analyzer.opt (Wanalyzer-unterminated-string): Delete. * call-details.cc (call_details::check_for_null_terminated_string_arg): Convert return type from void to const svalue *. Add param "out_sval". * call-details.h (call_details::check_for_null_terminated_string_arg): Likewise. * kf-analyzer.cc (kf_analyzer_get_strlen::impl_call_pre): Wire up to result of check_for_null_terminated_string_arg. * region-model.cc (get_strlen): Delete. (class unterminated_string_arg): Delete. (struct fragment): New. (class iterable_cluster): New. (region_model::get_store_bytes): New. (get_tree_for_byte_offset): New. (region_model::scan_for_null_terminator): New. (region_model::check_for_null_terminated_string_arg): Convert return type from void to const svalue *. Add param "out_sval". Reimplement in terms of scan_for_null_terminator, dropping the special-case for -Wanalyzer-unterminated-string. * region-model.h (region_model::get_store_bytes): New decl. (region_model::scan_for_null_terminator): New decl. (region_model::check_for_null_terminated_string_arg): Convert return type from void to const svalue *. Add param "out_sval". * store.cc (concrete_binding::get_byte_range): New. * store.h (concrete_binding::get_byte_range): New decl. (store_manager::get_concrete_binding): New overload. gcc/ChangeLog: PR analyzer/105899 * doc/invoke.texi: Remove -Wanalyzer-unterminated-string. gcc/testsuite/ChangeLog: PR analyzer/105899 * gcc.dg/analyzer/error-1.c: Update expected results to reflect reimplementation of unterminated string detection. Add test coverage for uninitialized buffers. * gcc.dg/analyzer/null-terminated-strings-1.c: Likewise. * gcc.dg/analyzer/putenv-1.c: Likewise. * gcc.dg/analyzer/strchr-1.c: Likewise. * gcc.dg/analyzer/strcpy-1.c: Likewise. * gcc.dg/analyzer/strdup-1.c: Likewise. Signed-off-by: David Malcolm <dmalcolm@redhat.com>
Diffstat (limited to 'gcc/analyzer')
-rw-r--r--gcc/analyzer/analyzer.opt4
-rw-r--r--gcc/analyzer/call-details.cc8
-rw-r--r--gcc/analyzer/call-details.h4
-rw-r--r--gcc/analyzer/kf-analyzer.cc15
-rw-r--r--gcc/analyzer/region-model.cc521
-rw-r--r--gcc/analyzer/region-model.h13
-rw-r--r--gcc/analyzer/store.cc9
-rw-r--r--gcc/analyzer/store.h7
8 files changed, 484 insertions, 97 deletions
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 6658ac5..7917473 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -214,10 +214,6 @@ Wanalyzer-tainted-size
Common Var(warn_analyzer_tainted_size) Init(1) Warning
Warn about code paths in which an unsanitized value is used as a size.
-Wanalyzer-unterminated-string
-Common Var(warn_analyzer_unterminated_string) Init(1) Warning
-Warn about code paths which attempt to find the length of an unterminated string.
-
Wanalyzer-use-after-free
Common Var(warn_analyzer_use_after_free) Init(1) Warning
Warn about code paths in which a freed value is used.
diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc
index fa86f55..e497fc5 100644
--- a/gcc/analyzer/call-details.cc
+++ b/gcc/analyzer/call-details.cc
@@ -376,11 +376,13 @@ call_details::lookup_function_attribute (const char *attr_name) const
return lookup_attribute (attr_name, TYPE_ATTRIBUTES (allocfntype));
}
-void
-call_details::check_for_null_terminated_string_arg (unsigned arg_idx) const
+const svalue *
+call_details::
+check_for_null_terminated_string_arg (unsigned arg_idx,
+ const svalue **out_sval) const
{
region_model *model = get_model ();
- model->check_for_null_terminated_string_arg (*this, arg_idx);
+ return model->check_for_null_terminated_string_arg (*this, arg_idx, out_sval);
}
} // namespace ana
diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h
index 0622cab..86f0e68 100644
--- a/gcc/analyzer/call-details.h
+++ b/gcc/analyzer/call-details.h
@@ -71,7 +71,9 @@ public:
tree lookup_function_attribute (const char *attr_name) const;
- void check_for_null_terminated_string_arg (unsigned arg_idx) const;
+ const svalue *
+ check_for_null_terminated_string_arg (unsigned arg_idx,
+ const svalue **out_sval = nullptr) const;
private:
const gcall *m_call;
diff --git a/gcc/analyzer/kf-analyzer.cc b/gcc/analyzer/kf-analyzer.cc
index 1a0c940..c767ebc 100644
--- a/gcc/analyzer/kf-analyzer.cc
+++ b/gcc/analyzer/kf-analyzer.cc
@@ -369,8 +369,19 @@ public:
}
void impl_call_pre (const call_details &cd) const final override
{
- cd.check_for_null_terminated_string_arg (0);
- cd.set_any_lhs_with_defaults ();
+ if (const svalue *bytes_read = cd.check_for_null_terminated_string_arg (0))
+ {
+ region_model_manager *mgr = cd.get_manager ();
+ /* strlen is (bytes_read - 1). */
+ const svalue *strlen_sval
+ = mgr->get_or_create_binop (size_type_node,
+ MINUS_EXPR,
+ bytes_read,
+ mgr->get_or_create_int_cst (size_type_node, 1));
+ cd.maybe_set_lhs (strlen_sval);
+ }
+ else
+ cd.set_any_lhs_with_defaults ();
}
};
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index ed93fb8..0fce188 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -3175,26 +3175,6 @@ region_model::set_value (tree lhs, tree rhs, region_model_context *ctxt)
set_value (lhs_reg, rhs_sval, ctxt);
}
-/* Look for the first 0 byte within STRING_CST.
- If there is one, write its index to *OUT and return true.
- Otherwise, return false. */
-
-static bool
-get_strlen (tree string_cst, int *out)
-{
- gcc_assert (TREE_CODE (string_cst) == STRING_CST);
-
- if (const void *p = memchr (TREE_STRING_POINTER (string_cst),
- 0,
- TREE_STRING_LENGTH (string_cst)))
- {
- *out = (const char *)p - TREE_STRING_POINTER (string_cst);
- return true;
- }
- else
- return false;
-}
-
/* A bundle of information about a problematic argument at a callsite
for use by pending_diagnostic subclasses for reporting and
for deduplication. */
@@ -3236,106 +3216,477 @@ inform_about_expected_null_terminated_string_arg (const call_arg_details &ad)
ad.m_arg_idx + 1, ad.m_called_fndecl);
}
-/* A subclass of pending_diagnostic for complaining about uses
- of unterminated strings (thus accessing beyond the bounds
- of a buffer). */
+/* A binding of a specific svalue at a concrete byte range. */
-class unterminated_string_arg
-: public pending_diagnostic_subclass<unterminated_string_arg>
+struct fragment
{
-public:
- unterminated_string_arg (const call_arg_details arg_details)
- : m_arg_details (arg_details)
+ fragment ()
+ : m_byte_range (0, 0), m_sval (nullptr)
{
- gcc_assert (m_arg_details.m_called_fndecl);
}
- const char *get_kind () const final override
+ fragment (const byte_range &bytes, const svalue *sval)
+ : m_byte_range (bytes), m_sval (sval)
{
- return "unterminated_string_arg";
}
- bool operator== (const unterminated_string_arg &other) const
+ static int cmp_ptrs (const void *p1, const void *p2)
{
- return m_arg_details == other.m_arg_details;
+ const fragment *f1 = (const fragment *)p1;
+ const fragment *f2 = (const fragment *)p2;
+ return byte_range::cmp (f1->m_byte_range, f2->m_byte_range);
}
- int get_controlling_option () const final override
+ /* Determine if there is a zero terminator somewhere in the
+ bytes of this fragment, starting at START_READ_OFFSET (which
+ is absolute to the start of the cluster as a whole), and stopping
+ at the end of this fragment.
+
+ Return a tristate:
+ - true if there definitely is a zero byte, writing to *OUT_BYTES_READ
+ the number of bytes from that would be read, including the zero byte.
+ - false if there definitely isn't a zero byte
+ - unknown if we don't know. */
+ tristate has_null_terminator (byte_offset_t start_read_offset,
+ byte_offset_t *out_bytes_read) const
{
- return OPT_Wanalyzer_unterminated_string;
+ byte_offset_t rel_start_read_offset
+ = start_read_offset - m_byte_range.get_start_byte_offset ();
+ gcc_assert (rel_start_read_offset >= 0);
+ byte_offset_t available_bytes
+ = (m_byte_range.get_next_byte_offset () - start_read_offset);
+ gcc_assert (available_bytes >= 0);
+
+ if (rel_start_read_offset > INT_MAX)
+ return tristate::TS_UNKNOWN;
+ HOST_WIDE_INT rel_start_read_offset_hwi = rel_start_read_offset.slow ();
+
+ if (available_bytes > INT_MAX)
+ return tristate::TS_UNKNOWN;
+ HOST_WIDE_INT available_bytes_hwi = available_bytes.slow ();
+
+ switch (m_sval->get_kind ())
+ {
+ case SK_CONSTANT:
+ {
+ tree cst
+ = as_a <const constant_svalue *> (m_sval)->get_constant ();
+ switch (TREE_CODE (cst))
+ {
+ case STRING_CST:
+ {
+ /* Look for the first 0 byte within STRING_CST
+ from START_READ_OFFSET onwards. */
+ const HOST_WIDE_INT num_bytes_to_search
+ = std::min<HOST_WIDE_INT> ((TREE_STRING_LENGTH (cst)
+ - rel_start_read_offset_hwi),
+ available_bytes_hwi);
+ const char *start = (TREE_STRING_POINTER (cst)
+ + rel_start_read_offset_hwi);
+ if (num_bytes_to_search >= 0)
+ if (const void *p = memchr (start, 0,
+ num_bytes_to_search))
+ {
+ *out_bytes_read = (const char *)p - start + 1;
+ return tristate (true);
+ }
+
+ *out_bytes_read = available_bytes;
+ return tristate (false);
+ }
+ break;
+ case INTEGER_CST:
+ if (rel_start_read_offset_hwi == 0
+ && integer_onep (TYPE_SIZE_UNIT (TREE_TYPE (cst))))
+ {
+ /* Model accesses to the initial byte of a 1-byte
+ INTEGER_CST. */
+ if (zerop (cst))
+ {
+ *out_bytes_read = 1;
+ return tristate (true);
+ }
+ else
+ {
+ *out_bytes_read = available_bytes;
+ return tristate (false);
+ }
+ }
+ /* Treat any other access to an INTEGER_CST as unknown. */
+ return tristate::TS_UNKNOWN;
+
+ default:
+ gcc_unreachable ();
+ break;
+ }
+ }
+ break;
+ default:
+ // TODO: it may be possible to handle other cases here.
+ return tristate::TS_UNKNOWN;
+ }
}
- bool emit (rich_location *rich_loc, logger *) final override
+ byte_range m_byte_range;
+ const svalue *m_sval;
+};
+
+/* A frozen copy of a single base region's binding_cluster within a store,
+ optimized for traversal of the concrete parts in byte order.
+ This only captures concrete bindings, and is an implementation detail
+ of region_model::scan_for_null_terminator. */
+
+class iterable_cluster
+{
+public:
+ iterable_cluster (const binding_cluster *cluster)
{
- auto_diagnostic_group d;
- bool warned;
- if (m_arg_details.m_arg_expr)
- warned = warning_at (rich_loc, get_controlling_option (),
- "passing pointer to unterminated string %qE"
- " as argument %i of %qE",
- m_arg_details.m_arg_expr,
- m_arg_details.m_arg_idx + 1,
- m_arg_details.m_called_fndecl);
- else
- warned = warning_at (rich_loc, get_controlling_option (),
- "passing pointer to unterminated string"
- " as argument %i of %qE",
- m_arg_details.m_arg_idx + 1,
- m_arg_details.m_called_fndecl);
- if (warned)
- inform_about_expected_null_terminated_string_arg (m_arg_details);
- return warned;
+ if (!cluster)
+ return;
+ for (auto iter : *cluster)
+ {
+ const binding_key *key = iter.first;
+ const svalue *sval = iter.second;
+
+ if (const concrete_binding *concrete_key
+ = key->dyn_cast_concrete_binding ())
+ {
+ byte_range fragment_bytes (0, 0);
+ if (concrete_key->get_byte_range (&fragment_bytes))
+ m_fragments.safe_push (fragment (fragment_bytes, sval));
+ }
+ }
+ m_fragments.qsort (fragment::cmp_ptrs);
}
- label_text describe_final_event (const evdesc::final_event &ev) final override
+ bool
+ get_fragment_for_byte (byte_offset_t byte, fragment *out_frag) const
{
- return ev.formatted_print
- ("passing pointer to unterminated buffer as argument %i of %qE"
- " would lead to read past the end of the buffer",
- m_arg_details.m_arg_idx + 1,
- m_arg_details.m_called_fndecl);
+ /* TODO: binary search rather than linear. */
+ unsigned iter_idx;
+ for (iter_idx = 0; iter_idx < m_fragments.length (); iter_idx++)
+ {
+ if (m_fragments[iter_idx].m_byte_range.contains_p (byte))
+ {
+ *out_frag = m_fragments[iter_idx];
+ return true;
+ }
+ }
+ return false;
}
private:
- const call_arg_details m_arg_details;
+ auto_vec<fragment> m_fragments;
};
+/* Simulate reading the bytes at BYTES from BASE_REG.
+ Complain to CTXT about any issues with the read e.g. out-of-bounds. */
+
+const svalue *
+region_model::get_store_bytes (const region *base_reg,
+ const byte_range &bytes,
+ region_model_context *ctxt) const
+{
+ const svalue *index_sval
+ = m_mgr->get_or_create_int_cst (size_type_node,
+ bytes.get_start_byte_offset ());
+ const region *offset_reg = m_mgr->get_offset_region (base_reg,
+ NULL_TREE,
+ index_sval);
+ const svalue *byte_size_sval
+ = m_mgr->get_or_create_int_cst (size_type_node, bytes.m_size_in_bytes);
+ const region *read_reg = m_mgr->get_sized_region (offset_reg,
+ NULL_TREE,
+ byte_size_sval);
+
+ /* Simulate reading those bytes from the store. */
+ const svalue *sval = get_store_value (read_reg, ctxt);
+ return sval;
+}
+
+static tree
+get_tree_for_byte_offset (tree ptr_expr, byte_offset_t byte_offset)
+{
+ gcc_assert (ptr_expr);
+ return fold_build2 (MEM_REF,
+ char_type_node,
+ ptr_expr, wide_int_to_tree (size_type_node, byte_offset));
+}
+
+/* Simulate a series of reads of REG until we find a 0 byte
+ (equivalent to calling strlen).
+
+ Complain to CTXT and return NULL if:
+ - the buffer pointed to isn't null-terminated
+ - the buffer pointed to has any uninitalized bytes before any 0-terminator
+ - any of the reads aren't within the bounds of the underlying base region
+
+ Otherwise, return a svalue for the number of bytes read (strlen + 1),
+ and, if OUT_SVAL is non-NULL, write to *OUT_SVAL with an svalue
+ representing the content of REG up to and including the terminator.
+
+ Algorithm
+ =========
+
+ Get offset for first byte to read.
+ Find the binding (if any) that contains it.
+ Find the size in bits of that binding.
+ Round to the nearest byte (which way???)
+ Or maybe give up if we have a partial binding there.
+ Get the svalue from the binding.
+ Determine the strlen (if any) of that svalue.
+ Does it have a 0-terminator within it?
+ If so, we have a partial read up to and including that terminator
+ Read those bytes from the store; add to the result in the correct place.
+ Finish
+ If not, we have a full read of that svalue
+ Read those bytes from the store; add to the result in the correct place.
+ Update read/write offsets
+ Continue
+ If unknown:
+ Result is unknown
+ Finish
+*/
+
+const svalue *
+region_model::scan_for_null_terminator (const region *reg,
+ tree expr,
+ const svalue **out_sval,
+ region_model_context *ctxt) const
+{
+ store_manager *store_mgr = m_mgr->get_store_manager ();
+
+ region_offset offset = reg->get_offset (m_mgr);
+ if (offset.symbolic_p ())
+ {
+ if (out_sval)
+ *out_sval = m_mgr->get_or_create_unknown_svalue (NULL_TREE);
+ return m_mgr->get_or_create_unknown_svalue (size_type_node);
+ }
+ byte_offset_t src_byte_offset;
+ if (!offset.get_concrete_byte_offset (&src_byte_offset))
+ {
+ if (out_sval)
+ *out_sval = m_mgr->get_or_create_unknown_svalue (NULL_TREE);
+ return m_mgr->get_or_create_unknown_svalue (size_type_node);
+ }
+ const byte_offset_t initial_src_byte_offset = src_byte_offset;
+ byte_offset_t dst_byte_offset = 0;
+
+ const region *base_reg = reg->get_base_region ();
+
+ if (const string_region *str_reg = base_reg->dyn_cast_string_region ())
+ {
+ tree string_cst = str_reg->get_string_cst ();
+ if (const void *p = memchr (TREE_STRING_POINTER (string_cst),
+ 0,
+ TREE_STRING_LENGTH (string_cst)))
+ {
+ size_t num_bytes_read
+ = (const char *)p - TREE_STRING_POINTER (string_cst) + 1;
+ /* Simulate the read. */
+ byte_range bytes_to_read (0, num_bytes_read);
+ const svalue *sval = get_store_bytes (reg, bytes_to_read, ctxt);
+ if (out_sval)
+ *out_sval = sval;
+ return m_mgr->get_or_create_int_cst (size_type_node,
+ num_bytes_read);
+ }
+ }
+
+ const binding_cluster *cluster = m_store.get_cluster (base_reg);
+ iterable_cluster c (cluster);
+ binding_map result;
+
+ while (1)
+ {
+ fragment f;
+ if (c.get_fragment_for_byte (src_byte_offset, &f))
+ {
+ byte_offset_t fragment_bytes_read;
+ tristate is_terminated
+ = f.has_null_terminator (src_byte_offset, &fragment_bytes_read);
+ if (is_terminated.is_unknown ())
+ {
+ if (out_sval)
+ *out_sval = m_mgr->get_or_create_unknown_svalue (NULL_TREE);
+ return m_mgr->get_or_create_unknown_svalue (size_type_node);
+ }
+
+ /* Simulate reading those bytes from the store. */
+ byte_range bytes_to_read (src_byte_offset, fragment_bytes_read);
+ const svalue *sval = get_store_bytes (base_reg, bytes_to_read, ctxt);
+ check_for_poison (sval, expr, nullptr, ctxt);
+
+ if (out_sval)
+ {
+ byte_range bytes_to_write (dst_byte_offset, fragment_bytes_read);
+ const binding_key *key
+ = store_mgr->get_concrete_binding (bytes_to_write);
+ result.put (key, sval);
+ }
+
+ src_byte_offset += fragment_bytes_read;
+ dst_byte_offset += fragment_bytes_read;
+
+ if (is_terminated.is_true ())
+ {
+ if (out_sval)
+ *out_sval = m_mgr->get_or_create_compound_svalue (NULL_TREE,
+ result);
+ return m_mgr->get_or_create_int_cst (size_type_node,
+ dst_byte_offset);
+ }
+ }
+ else
+ break;
+ }
+
+ /* No binding for this base_region, or no binding at src_byte_offset
+ (or a symbolic binding). */
+
+ /* TODO: the various special-cases seen in
+ region_model::get_store_value. */
+
+ /* Simulate reading from this byte, then give up. */
+ byte_range bytes_to_read (src_byte_offset, 1);
+ const svalue *sval = get_store_bytes (base_reg, bytes_to_read, ctxt);
+ tree byte_expr
+ = get_tree_for_byte_offset (expr,
+ src_byte_offset - initial_src_byte_offset);
+ check_for_poison (sval, byte_expr, nullptr, ctxt);
+ if (base_reg->can_have_initial_svalue_p ())
+ {
+ if (out_sval)
+ *out_sval = m_mgr->get_or_create_unknown_svalue (NULL_TREE);
+ return m_mgr->get_or_create_unknown_svalue (size_type_node);
+ }
+ else
+ return nullptr;
+}
+
/* Check that argument ARG_IDX (0-based) to the call described by CD
is a pointer to a valid null-terminated string.
- Complain if the buffer pointed to isn't null-terminated.
+ Simulate scanning through the buffer, reading until we find a 0 byte
+ (equivalent to calling strlen).
- TODO: we should also complain if:
- - the pointer is NULL (or could be)
- - the buffer pointed to is uninitalized before any 0-terminator
- - the 0-terminator is within the bounds of the underlying base region
+ Complain and return NULL if:
+ - the buffer pointed to isn't null-terminated
+ - the buffer pointed to has any uninitalized bytes before any 0-terminator
+ - any of the reads aren't within the bounds of the underlying base region
- We're checking that the called function could validly iterate through
- the buffer reading it until it finds a 0 byte (such as by calling
- strlen, or equivalent code). */
+ Otherwise, return a svalue for the number of bytes read (strlen + 1),
+ and, if OUT_SVAL is non-NULL, write to *OUT_SVAL with an svalue
+ representing the content of the buffer up to and including the terminator.
-void
+ TODO: we should also complain if:
+ - the pointer is NULL (or could be). */
+
+const svalue *
region_model::check_for_null_terminated_string_arg (const call_details &cd,
- unsigned arg_idx)
+ unsigned arg_idx,
+ const svalue **out_sval)
{
- region_model_context *ctxt = cd.get_ctxt ();
+ class null_terminator_check_event : public custom_event
+ {
+ public:
+ null_terminator_check_event (const event_loc_info &loc_info,
+ const call_arg_details &arg_details)
+ : custom_event (loc_info),
+ m_arg_details (arg_details)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const final override
+ {
+ if (m_arg_details.m_arg_expr)
+ return make_label_text (can_colorize,
+ "while looking for null terminator"
+ " for argument %i (%qE) of %qD...",
+ m_arg_details.m_arg_idx + 1,
+ m_arg_details.m_arg_expr,
+ m_arg_details.m_called_fndecl);
+ else
+ return make_label_text (can_colorize,
+ "while looking for null terminator"
+ " for argument %i of %qD...",
+ m_arg_details.m_arg_idx + 1,
+ m_arg_details.m_called_fndecl);
+ }
+
+ private:
+ const call_arg_details m_arg_details;
+ };
+
+ class null_terminator_check_decl_note
+ : public pending_note_subclass<null_terminator_check_decl_note>
+ {
+ public:
+ null_terminator_check_decl_note (const call_arg_details &arg_details)
+ : m_arg_details (arg_details)
+ {
+ }
+
+ const char *get_kind () const final override
+ {
+ return "null_terminator_check_decl_note";
+ }
+
+ void emit () const final override
+ {
+ inform_about_expected_null_terminated_string_arg (m_arg_details);
+ }
+
+ bool operator== (const null_terminator_check_decl_note &other) const
+ {
+ return m_arg_details == other.m_arg_details;
+ }
+
+ private:
+ const call_arg_details m_arg_details;
+ };
+
+ /* Subclass of decorated_region_model_context that
+ adds the above event and note to any saved diagnostics. */
+ class annotating_ctxt : public annotating_context
+ {
+ public:
+ annotating_ctxt (const call_details &cd,
+ unsigned arg_idx)
+ : annotating_context (cd.get_ctxt ()),
+ m_cd (cd),
+ m_arg_idx (arg_idx)
+ {
+ }
+ void add_annotations () final override
+ {
+ call_arg_details arg_details (m_cd, m_arg_idx);
+ event_loc_info loc_info (m_cd.get_location (),
+ m_cd.get_model ()->get_current_function ()->decl,
+ m_cd.get_model ()->get_stack_depth ());
+
+ add_event (make_unique<null_terminator_check_event> (loc_info,
+ arg_details));
+ add_note (make_unique <null_terminator_check_decl_note> (arg_details));
+ }
+ private:
+ const call_details &m_cd;
+ unsigned m_arg_idx;
+ };
+
+ /* Use this ctxt below so that any diagnostics that get added
+ get annotated. */
+ annotating_ctxt my_ctxt (cd, arg_idx);
const svalue *arg_sval = cd.get_arg_svalue (arg_idx);
const region *buf_reg
- = deref_rvalue (arg_sval, cd.get_arg_tree (arg_idx), ctxt);
-
- const svalue *contents_sval = get_store_value (buf_reg, ctxt);
+ = deref_rvalue (arg_sval, cd.get_arg_tree (arg_idx), &my_ctxt);
- if (tree cst = contents_sval->maybe_get_constant ())
- if (TREE_CODE (cst) == STRING_CST)
- {
- int cst_strlen;
- if (!get_strlen (cst, &cst_strlen))
- {
- call_arg_details arg_details (cd, arg_idx);
- ctxt->warn (make_unique<unterminated_string_arg> (arg_details));
- }
- }
+ return scan_for_null_terminator (buf_reg,
+ cd.get_arg_tree (arg_idx),
+ out_sval,
+ &my_ctxt);
}
/* Remove all bindings overlapping REG within the store. */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index a01399c..63a67b3 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -451,6 +451,13 @@ class region_model
const svalue *get_store_value (const region *reg,
region_model_context *ctxt) const;
+ const svalue *get_store_bytes (const region *base_reg,
+ const byte_range &bytes,
+ region_model_context *ctxt) const;
+ const svalue *scan_for_null_terminator (const region *reg,
+ tree expr,
+ const svalue **out_sval,
+ region_model_context *ctxt) const;
bool region_exists_p (const region *reg) const;
@@ -502,8 +509,10 @@ class region_model
const svalue *sval_hint,
region_model_context *ctxt) const;
- void check_for_null_terminated_string_arg (const call_details &cd,
- unsigned idx);
+ const svalue *
+ check_for_null_terminated_string_arg (const call_details &cd,
+ unsigned idx,
+ const svalue **out_sval = nullptr);
private:
const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc
index c7bc4b4..aeea693 100644
--- a/gcc/analyzer/store.cc
+++ b/gcc/analyzer/store.cc
@@ -538,6 +538,15 @@ concrete_binding::overlaps_p (const concrete_binding &other) const
return false;
}
+/* If this is expressible as a concrete byte range, return true
+ and write it to *OUT. Otherwise return false. */
+
+bool
+concrete_binding::get_byte_range (byte_range *out) const
+{
+ return m_bit_range.as_byte_range (out);
+}
+
/* Comparator for use by vec<const concrete_binding *>::qsort. */
int
diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h
index af6cc7e..cf10fa3 100644
--- a/gcc/analyzer/store.h
+++ b/gcc/analyzer/store.h
@@ -399,6 +399,7 @@ public:
{ return this; }
const bit_range &get_bit_range () const { return m_bit_range; }
+ bool get_byte_range (byte_range *out) const;
bit_offset_t get_start_bit_offset () const
{
@@ -855,6 +856,12 @@ public:
return get_concrete_binding (bits.get_start_bit_offset (),
bits.m_size_in_bits);
}
+ const concrete_binding *
+ get_concrete_binding (const byte_range &bytes)
+ {
+ bit_range bits = bytes.as_bit_range ();
+ return get_concrete_binding (bits);
+ }
const symbolic_binding *
get_symbolic_binding (const region *region);