aboutsummaryrefslogtreecommitdiff
path: root/gcc/analyzer/sm-malloc.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/analyzer/sm-malloc.cc')
-rw-r--r--gcc/analyzer/sm-malloc.cc164
1 files changed, 125 insertions, 39 deletions
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 38a2f1e..ba6d41c 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -35,6 +35,12 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h"
#include "analyzer/pending-diagnostic.h"
+#include "tristate.h"
+#include "selftest.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
#if ENABLE_ANALYZER
@@ -53,6 +59,23 @@ public:
bool inherited_state_p () const FINAL OVERRIDE { return false; }
+ state_machine::state_t
+ get_default_state (const svalue *sval) const FINAL OVERRIDE
+ {
+ if (tree cst = sval->maybe_get_constant ())
+ {
+ if (zerop (cst))
+ return m_null;
+ }
+ if (const region_svalue *ptr = sval->dyn_cast_region_svalue ())
+ {
+ const region *reg = ptr->get_pointee ();
+ if (reg->get_kind () == RK_STRING)
+ return m_non_heap;
+ }
+ return m_start;
+ }
+
bool on_stmt (sm_context *sm_ctxt,
const supernode *node,
const gimple *stmt) const FINAL OVERRIDE;
@@ -72,6 +95,9 @@ public:
bool can_purge_p (state_t s) const FINAL OVERRIDE;
pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
+ bool reset_when_passed_to_unknown_fn_p (state_t s,
+ bool is_mutable) const FINAL OVERRIDE;
+
/* Start state. */
state_t m_start;
@@ -127,16 +153,34 @@ public:
return label_text::borrow ("allocated here");
if (change.m_old_state == m_sm.m_unchecked
&& change.m_new_state == m_sm.m_nonnull)
- return change.formatted_print ("assuming %qE is non-NULL",
- change.m_expr);
+ {
+ if (change.m_expr)
+ return change.formatted_print ("assuming %qE is non-NULL",
+ change.m_expr);
+ else
+ return change.formatted_print ("assuming %qs is non-NULL",
+ "<unknown>");
+ }
if (change.m_new_state == m_sm.m_null)
{
if (change.m_old_state == m_sm.m_unchecked)
- return change.formatted_print ("assuming %qE is NULL",
- change.m_expr);
+ {
+ if (change.m_expr)
+ return change.formatted_print ("assuming %qE is NULL",
+ change.m_expr);
+ else
+ return change.formatted_print ("assuming %qs is NULL",
+ "<unknown>");
+ }
else
- return change.formatted_print ("%qE is NULL",
- change.m_expr);
+ {
+ if (change.m_expr)
+ return change.formatted_print ("%qE is NULL",
+ change.m_expr);
+ else
+ return change.formatted_print ("%qs is NULL",
+ "<unknown>");
+ }
}
return label_text ();
@@ -406,9 +450,15 @@ public:
auto_diagnostic_group d;
diagnostic_metadata m;
m.add_cwe (690);
- bool warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
- "use of NULL %qE where non-null expected",
- m_arg);
+
+ bool warned;
+ if (zerop (m_arg))
+ warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
+ "use of NULL where non-null expected");
+ else
+ warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
+ "use of NULL %qE where non-null expected",
+ m_arg);
if (warned)
inform_nonnull_attribute (m_fndecl, m_arg_idx);
return warned;
@@ -416,9 +466,13 @@ public:
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
{
- return ev.formatted_print ("argument %u (%qE) NULL"
- " where non-null expected",
- m_arg_idx + 1, ev.m_expr);
+ if (zerop (ev.m_expr))
+ return ev.formatted_print ("argument %u NULL where non-null expected",
+ m_arg_idx + 1);
+ else
+ return ev.formatted_print ("argument %u (%qE) NULL"
+ " where non-null expected",
+ m_arg_idx + 1, ev.m_expr);
}
private:
@@ -480,8 +534,12 @@ public:
{
diagnostic_metadata m;
m.add_cwe (401);
- return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
- "leak of %qE", m_arg);
+ if (m_arg)
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
+ "leak of %qE", m_arg);
+ else
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
+ "leak of %qs", "<unknown>");
}
label_text describe_state_change (const evdesc::state_change &change)
@@ -497,11 +555,22 @@ public:
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
{
- if (m_malloc_event.known_p ())
- return ev.formatted_print ("%qE leaks here; was allocated at %@",
- ev.m_expr, &m_malloc_event);
+ if (ev.m_expr)
+ {
+ if (m_malloc_event.known_p ())
+ return ev.formatted_print ("%qE leaks here; was allocated at %@",
+ ev.m_expr, &m_malloc_event);
+ else
+ return ev.formatted_print ("%qE leaks here", ev.m_expr);
+ }
else
- return ev.formatted_print ("%qE leaks here", ev.m_expr);
+ {
+ if (m_malloc_event.known_p ())
+ return ev.formatted_print ("%qs leaks here; was allocated at %@",
+ "<unknown>", &m_malloc_event);
+ else
+ return ev.formatted_print ("%qs leaks here", "<unknown>");
+ }
}
private:
@@ -618,10 +687,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
{
tree lhs = gimple_call_lhs (call);
if (lhs)
- {
- lhs = sm_ctxt->get_readable_tree (lhs);
- sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
- }
+ sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
else
{
/* TODO: report leak. */
@@ -634,10 +700,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
{
tree lhs = gimple_call_lhs (call);
if (lhs)
- {
- lhs = sm_ctxt->get_readable_tree (lhs);
- sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
- }
+ sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
return true;
}
@@ -646,8 +709,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|| is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
{
tree arg = gimple_call_arg (call, 0);
-
- arg = sm_ctxt->get_readable_tree (arg);
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
/* start/unchecked/nonnull -> freed. */
sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
@@ -659,12 +721,12 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
/* freed -> stop, with warning. */
sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
- new double_free (*this, arg));
+ new double_free (*this, diag_arg));
sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
/* non-heap -> stop, with warning. */
sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
- new free_of_non_heap (*this, arg));
+ new free_of_non_heap (*this, diag_arg));
sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
return true;
}
@@ -685,15 +747,17 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
if (bitmap_empty_p (nonnull_args)
|| bitmap_bit_p (nonnull_args, i))
{
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
sm_ctxt->warn_for_state
(node, stmt, arg, m_unchecked,
- new possible_null_arg (*this, arg, callee_fndecl, i));
+ new possible_null_arg (*this, diag_arg, callee_fndecl,
+ i));
sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
m_nonnull);
sm_ctxt->warn_for_state
(node, stmt, arg, m_null,
- new null_arg (*this, arg, callee_fndecl, i));
+ new null_arg (*this, diag_arg, callee_fndecl, i));
sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
}
}
@@ -702,10 +766,15 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
}
}
- if (tree lhs = is_zero_assignment (stmt))
+ if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
if (any_pointer_p (lhs))
on_zero_assignment (sm_ctxt, node, stmt,lhs);
+ /* If we have "LHS = &EXPR;" and EXPR is something other than a MEM_REF,
+ transition LHS from start to non_heap.
+ Doing it for ADDR_EXPR(MEM_REF()) is likely wrong, and can lead to
+ unbounded chains of unmergeable sm-state on pointer arithmetic in loops
+ when optimization is enabled. */
if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
{
enum tree_code op = gimple_assign_rhs_code (assign_stmt);
@@ -714,8 +783,9 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
tree lhs = gimple_assign_lhs (assign_stmt);
if (lhs)
{
- lhs = sm_ctxt->get_readable_tree (lhs);
- sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
+ tree addr_expr = gimple_assign_rhs1 (assign_stmt);
+ if (TREE_CODE (TREE_OPERAND (addr_expr, 0)) != MEM_REF)
+ sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
}
}
}
@@ -732,18 +802,18 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
if (TREE_CODE (op) == MEM_REF)
{
tree arg = TREE_OPERAND (op, 0);
- arg = sm_ctxt->get_readable_tree (arg);
+ tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
- new possible_null_deref (*this, arg));
+ new possible_null_deref (*this, diag_arg));
sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
sm_ctxt->warn_for_state (node, stmt, arg, m_null,
- new null_deref (*this, arg));
+ new null_deref (*this, diag_arg));
sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
- new use_after_free (*this, arg));
+ new use_after_free (*this, diag_arg));
sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
}
}
@@ -818,6 +888,22 @@ malloc_state_machine::on_leak (tree var) const
return new malloc_leak (*this, var);
}
+/* Implementation of state_machine::reset_when_passed_to_unknown_fn_p vfunc
+ for malloc_state_machine. */
+
+bool
+malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s,
+ bool is_mutable) const
+{
+ /* An on-stack ptr doesn't stop being stack-allocated when passed to an
+ unknown fn. */
+ if (s == m_non_heap)
+ return false;
+
+ /* Otherwise, pointers passed as non-const can be freed. */
+ return is_mutable;
+}
+
/* Shared logic for handling GIMPLE_ASSIGNs and GIMPLE_PHIs that
assign zero to LHS. */