Commit ef7827b0 by David Malcolm

analyzer: purge state for unknown function calls

Whilst analyzing the reproducer for detecting CVE-2005-1689
(krb5-1.4.1's src/lib/krb5/krb/recvauth.c), the analyzer reports
a false double-free of the form:

  krb5_xfree(inbuf.data);
  krb5_read_message(..., &inbuf);
  krb5_xfree(inbuf.data); /* false diagnostic here.  */

where the call to krb5_read_message overwrites inbuf.data with
a freshly-malloced buffer.

This patch fixes the issue by purging state more thorougly when
handling a call with unknown behavior, by walking the graph of
memory regions that are reachable from the call.

gcc/analyzer/ChangeLog:
	* analyzer.h (fndecl_has_gimple_body_p): New decl.
	* engine.cc (impl_region_model_context::on_unknown_change): New
	function.
	(fndecl_has_gimple_body_p): Make non-static.
	(exploded_node::on_stmt): Treat __analyzer_dump_exploded_nodes as
	known.  Track whether we have a call with unknown side-effects and
	pass it to on_call_post.
	* exploded-graph.h (impl_region_model_context::on_unknown_change):
	New decl.
	* program-state.cc (sm_state_map::on_unknown_change): New function.
	* program-state.h (sm_state_map::on_unknown_change): New decl.
	* region-model.cc: Include "bitmap.h".
	(region_model::on_call_pre): Return a bool, capturing whether the
	call has unknown side effects.
	(region_model::on_call_post): Add arg "bool unknown_side_effects"
	and if true, call handle_unrecognized_call.
	(class reachable_regions): New class.
	(region_model::handle_unrecognized_call): New function.
	* region-model.h (region_model::on_call_pre): Return a bool.
	(region_model::on_call_post): Add arg "bool unknown_side_effects".
	(region_model::handle_unrecognized_call): New decl.
	(region_model_context::on_unknown_change): New vfunc.
	(test_region_model_context::on_unknown_change): New function.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/data-model-1.c: Remove xfail.
	* gcc.dg/analyzer/data-model-5b.c: Likewise.
	* gcc.dg/analyzer/data-model-5c.c: Likewise.
	* gcc.dg/analyzer/setjmp-3.c: Mark "foo" as pure.
	* gcc.dg/analyzer/setjmp-4.c: Likewise.
	* gcc.dg/analyzer/setjmp-6.c: Likewise.
	* gcc.dg/analyzer/setjmp-7.c: Likewise.
	* gcc.dg/analyzer/setjmp-7a.c: Likewise.
	* gcc.dg/analyzer/setjmp-8.c: Likewise.
	* gcc.dg/analyzer/setjmp-9.c: Likewise.
	* gcc.dg/analyzer/unknown-fns.c: New test.
parent 14f9d7b9
2020-01-14 David Malcolm <dmalcolm@redhat.com>
* analyzer.h (fndecl_has_gimple_body_p): New decl.
* engine.cc (impl_region_model_context::on_unknown_change): New
function.
(fndecl_has_gimple_body_p): Make non-static.
(exploded_node::on_stmt): Treat __analyzer_dump_exploded_nodes as
known. Track whether we have a call with unknown side-effects and
pass it to on_call_post.
* exploded-graph.h (impl_region_model_context::on_unknown_change):
New decl.
* program-state.cc (sm_state_map::on_unknown_change): New function.
* program-state.h (sm_state_map::on_unknown_change): New decl.
* region-model.cc: Include "bitmap.h".
(region_model::on_call_pre): Return a bool, capturing whether the
call has unknown side effects.
(region_model::on_call_post): Add arg "bool unknown_side_effects"
and if true, call handle_unrecognized_call.
(class reachable_regions): New class.
(region_model::handle_unrecognized_call): New function.
* region-model.h (region_model::on_call_pre): Return a bool.
(region_model::on_call_post): Add arg "bool unknown_side_effects".
(region_model::handle_unrecognized_call): New decl.
(region_model_context::on_unknown_change): New vfunc.
(test_region_model_context::on_unknown_change): New function.
2020-01-14 David Malcolm <dmalcolm@redhat.com>
* diagnostic-manager.cc (saved_diagnostic::operator==): Move here
from header. Replace pointer equality test on m_var with call to
pending_diagnostic::same_tree_p.
......
......@@ -80,6 +80,8 @@ extern void register_analyzer_pass ();
extern label_text make_label_text (bool can_colorize, const char *fmt, ...);
extern bool fndecl_has_gimple_body_p (tree fndecl);
/* An RAII-style class for pushing/popping cfun within a scope.
Doing so ensures we get "In function " announcements
from the diagnostics subsystem. */
......
......@@ -135,6 +135,15 @@ impl_region_model_context::on_svalue_purge (svalue_id first_unused_sid,
return total;
}
void
impl_region_model_context::on_unknown_change (svalue_id sid)
{
int sm_idx;
sm_state_map *smap;
FOR_EACH_VEC_ELT (m_new_state->m_checker_states, sm_idx, smap)
smap->on_unknown_change (sid);
}
/* class setjmp_svalue : public svalue. */
/* Compare the fields of this setjmp_svalue with OTHER, returning true
......@@ -876,7 +885,7 @@ exploded_node::dump (const extrinsic_state &ext_state) const
/* Return true if FNDECL has a gimple body. */
// TODO: is there a pre-canned way to do this?
static bool
bool
fndecl_has_gimple_body_p (tree fndecl)
{
if (fndecl == NULL_TREE)
......@@ -935,6 +944,10 @@ exploded_node::on_stmt (exploded_graph &eg,
if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
state->m_region_model->on_return (return_, &ctxt);
/* Track whether we have a gcall to a function that's not recognized by
anything, for which we don't have a function body, or for which we
don't know the fndecl. */
bool unknown_side_effects = false;
if (const gcall *call = dyn_cast <const gcall *> (stmt))
{
/* Debugging/test support. */
......@@ -977,6 +990,11 @@ exploded_node::on_stmt (exploded_graph &eg,
/* TODO: is there a good cross-platform way to do this? */
raise (SIGINT);
}
else if (is_special_named_call_p (call, "__analyzer_dump_exploded_nodes",
1))
{
/* This is handled elsewhere. */
}
else if (is_setjmp_call_p (stmt))
state->m_region_model->on_setjmp (call, this, &ctxt);
else if (is_longjmp_call_p (call))
......@@ -985,7 +1003,7 @@ exploded_node::on_stmt (exploded_graph &eg,
return on_stmt_flags::terminate_path ();
}
else
state->m_region_model->on_call_pre (call, &ctxt);
unknown_side_effects = state->m_region_model->on_call_pre (call, &ctxt);
}
bool any_sm_changes = false;
......@@ -1001,7 +1019,9 @@ exploded_node::on_stmt (exploded_graph &eg,
change,
old_smap, new_smap);
/* Allow the state_machine to handle the stmt. */
if (!sm.on_stmt (&ctxt, snode, stmt))
if (sm.on_stmt (&ctxt, snode, stmt))
unknown_side_effects = false;
else
{
/* For those stmts that were not handled by the state machine. */
if (const gcall *call = dyn_cast <const gcall *> (stmt))
......@@ -1019,7 +1039,7 @@ exploded_node::on_stmt (exploded_graph &eg,
}
if (const gcall *call = dyn_cast <const gcall *> (stmt))
state->m_region_model->on_call_post (call, &ctxt);
state->m_region_model->on_call_post (call, unknown_side_effects, &ctxt);
return on_stmt_flags (any_sm_changes);
}
......
......@@ -70,6 +70,8 @@ class impl_region_model_context : public region_model_context
void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
void on_unknown_change (svalue_id sid ATTRIBUTE_UNUSED) FINAL OVERRIDE;
exploded_graph *m_eg;
log_user m_logger;
const exploded_node *m_enode_for_diag;
......
......@@ -480,6 +480,14 @@ sm_state_map::on_cast (svalue_id src_sid,
impl_set_state (dst_sid, state, get_origin (src_sid));
}
/* Purge state from SID (in response to a call to an unknown function). */
void
sm_state_map::on_unknown_change (svalue_id sid)
{
impl_set_state (sid, (state_machine::state_t)0, svalue_id::null ());
}
/* Assert that this object is sane. */
void
......
......@@ -184,6 +184,8 @@ public:
void on_cast (svalue_id src_sid,
svalue_id dst_sid);
void on_unknown_change (svalue_id sid);
void validate (const state_machine &sm, int num_svalues) const;
iterator_t begin () const { return m_map.begin (); }
......
......@@ -39,6 +39,7 @@ along with GCC; see the file COPYING3. If not see
#include "diagnostic-metadata.h"
#include "diagnostic-core.h"
#include "tristate.h"
#include "bitmap.h"
#include "selftest.h"
#include "function.h"
#include "analyzer/analyzer.h"
......@@ -4115,9 +4116,13 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt)
Updates to the region_model that should be made *before* sm-states
are updated are done here; other updates to the region_model are done
in region_model::on_call_post. */
in region_model::on_call_post.
void
Return true if the function call has unknown side effects (it wasn't
recognized and we don't have a body for it, or are unable to tell which
fndecl it is). */
bool
region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
{
region_id lhs_rid;
......@@ -4135,6 +4140,8 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
for (unsigned i = 0; i < gimple_call_num_args (call); i++)
check_for_poison (gimple_call_arg (call, i), ctxt);
bool unknown_side_effects = false;
if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
{
if (is_named_call_p (callee_fndecl, "malloc", call, 1))
......@@ -4147,7 +4154,7 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
= get_or_create_ptr_svalue (lhs_type, new_rid);
set_value (lhs_rid, ptr_sid, ctxt);
}
return;
return false;
}
else if (is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1))
{
......@@ -4160,7 +4167,7 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
= get_or_create_ptr_svalue (lhs_type, new_rid);
set_value (lhs_rid, ptr_sid, ctxt);
}
return;
return false;
}
else if (is_named_call_p (callee_fndecl, "strlen", call, 1))
{
......@@ -4179,7 +4186,7 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
svalue_id result_sid
= get_or_create_constant_svalue (t_cst);
set_value (lhs_rid, result_sid, ctxt);
return;
return false;
}
}
/* Otherwise an unknown value. */
......@@ -4199,18 +4206,20 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
/* Use quotes to ensure the output isn't truncated. */
warning_at (call->location, 0,
"num heap regions: %qi", num_heap_regions);
return false;
}
else if (!fndecl_has_gimple_body_p (callee_fndecl)
&& !DECL_PURE_P (callee_fndecl))
unknown_side_effects = true;
}
/* Unrecognized call. */
else
unknown_side_effects = true;
/* Unknown return value. */
if (!lhs_rid.null_p ())
set_to_new_unknown_value (lhs_rid, lhs_type, ctxt);
/* TODO: also, any pointer arguments might have been written through,
or the things they point to (implying a graph traversal, which
presumably we need to do before overwriting the old value). */
return unknown_side_effects;
}
/* Update this model for the CALL stmt, using CTXT to report any
......@@ -4218,10 +4227,15 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
Updates to the region_model that should be made *after* sm-states
are updated are done here; other updates to the region_model are done
in region_model::on_call_pre. */
in region_model::on_call_pre.
If UNKNOWN_SIDE_EFFECTS is true, also call handle_unrecognized_call
to purge state. */
void
region_model::on_call_post (const gcall *call, region_model_context *ctxt)
region_model::on_call_post (const gcall *call,
bool unknown_side_effects,
region_model_context *ctxt)
{
/* Update for "free" here, after sm-handling.
......@@ -4264,6 +4278,185 @@ region_model::on_call_post (const gcall *call, region_model_context *ctxt)
}
return;
}
if (unknown_side_effects)
handle_unrecognized_call (call, ctxt);
}
/* Helper class for region_model::handle_unrecognized_call, for keeping
track of all regions that are reachable, and, of those, which are
mutable. */
class reachable_regions
{
public:
reachable_regions (region_model *model)
: m_model (model), m_reachable_rids (), m_mutable_rids ()
{}
/* Lazily mark RID as being reachable, recursively adding regions
reachable from RID. */
void add (region_id rid, bool is_mutable)
{
gcc_assert (!rid.null_p ());
unsigned idx = rid.as_int ();
/* Bail out if this region is already in the sets at the IS_MUTABLE
level of mutability. */
if (!is_mutable && bitmap_bit_p (m_reachable_rids, idx))
return;
bitmap_set_bit (m_reachable_rids, idx);
if (is_mutable)
{
if (bitmap_bit_p (m_mutable_rids, idx))
return;
else
bitmap_set_bit (m_mutable_rids, idx);
}
/* If this region's value is a pointer, add the pointee. */
region *reg = m_model->get_region (rid);
svalue_id sid = reg->get_value_direct ();
svalue *sval = m_model->get_svalue (sid);
if (sval)
if (region_svalue *ptr = sval->dyn_cast_region_svalue ())
{
region_id pointee_rid = ptr->get_pointee ();
/* Use const-ness of pointer type to affect mutability. */
bool ptr_is_mutable = true;
if (ptr->get_type ()
&& TREE_CODE (ptr->get_type ()) == POINTER_TYPE
&& TYPE_READONLY (TREE_TYPE (ptr->get_type ())))
ptr_is_mutable = false;
add (pointee_rid, ptr_is_mutable);
}
/* Add descendents of this region. */
region_id_set descendents (m_model);
m_model->get_descendents (rid, &descendents, region_id::null ());
for (unsigned i = 0; i < m_model->get_num_regions (); i++)
{
region_id iter_rid = region_id::from_int (i);
if (descendents.region_p (iter_rid))
add (iter_rid, is_mutable);
}
}
bool mutable_p (region_id rid)
{
gcc_assert (!rid.null_p ());
return bitmap_bit_p (m_mutable_rids, rid.as_int ());
}
private:
region_model *m_model;
/* The region ids already seen. This has to be an auto_bitmap rather than
an auto_sbitmap as new regions can be created within the model during
the traversal. */
auto_bitmap m_reachable_rids;
/* The region_ids that can be changed (accessed via non-const pointers). */
auto_bitmap m_mutable_rids;
};
/* Handle a call CALL to a function with unknown behavior.
Traverse the regions in this model, determining what regions are
reachable from pointer arguments to CALL and from global variables,
recursively.
Set all reachable regions to new unknown values and purge sm-state
from their values, and from values that point to them. */
void
region_model::handle_unrecognized_call (const gcall *call,
region_model_context *ctxt)
{
tree fndecl = get_fndecl_for_call (call, ctxt);
reachable_regions reachable_regions (this);
/* Determine the reachable regions and their mutability. */
{
/* Globals. */
region_id globals_rid = get_globals_region_id ();
if (!globals_rid.null_p ())
reachable_regions.add (globals_rid, true);
/* Params that are pointers. */
tree iter_param_types = NULL_TREE;
if (fndecl)
iter_param_types = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
for (unsigned arg_idx = 0; arg_idx < gimple_call_num_args (call); arg_idx++)
{
/* Track expected param type, where available. */
tree param_type = NULL_TREE;
if (iter_param_types)
{
param_type = TREE_VALUE (iter_param_types);
gcc_assert (param_type);
iter_param_types = TREE_CHAIN (iter_param_types);
}
tree parm = gimple_call_arg (call, arg_idx);
svalue_id parm_sid = get_rvalue (parm, NULL);
svalue *parm_sval = get_svalue (parm_sid);
if (parm_sval)
if (region_svalue *parm_ptr = parm_sval->dyn_cast_region_svalue ())
{
region_id pointee_rid = parm_ptr->get_pointee ();
bool is_mutable = true;
if (param_type
&& TREE_CODE (param_type) == POINTER_TYPE
&& TYPE_READONLY (TREE_TYPE (param_type)))
is_mutable = false;
reachable_regions.add (pointee_rid, is_mutable);
}
// FIXME: what about compound parms that contain ptrs?
}
}
/* OK: we now have all reachable regions.
Set them all to new unknown values. */
for (unsigned i = 0; i < get_num_regions (); i++)
{
region_id iter_rid = region_id::from_int (i);
if (reachable_regions.mutable_p (iter_rid))
{
region *reg = get_region (iter_rid);
/* Purge any sm-state for any underlying svalue. */
svalue_id curr_sid = reg->get_value_direct ();
if (!curr_sid.null_p ())
ctxt->on_unknown_change (curr_sid);
set_to_new_unknown_value (iter_rid,
reg->get_type (),
ctxt);
}
}
/* Purge sm-state for any remaining svalues that point to regions that
were reachable. This helps suppress leak false-positives.
For example, if we had a malloc call that was cast to a "foo *" type,
we could have a temporary void * for the result of malloc which has its
own svalue, not reachable from the function call, but for which the
"foo *" svalue was reachable. If we don't purge it, the temporary will
be reported as a leak. */
int i;
svalue *svalue;
FOR_EACH_VEC_ELT (m_svalues, i, svalue)
if (region_svalue *ptr = svalue->dyn_cast_region_svalue ())
{
region_id pointee_rid = ptr->get_pointee ();
if (reachable_regions.mutable_p (pointee_rid))
ctxt->on_unknown_change (svalue_id::from_int (i));
}
validate ();
}
/* Update this model for the RETURN_STMT, using CTXT to report any
......
......@@ -1578,8 +1578,12 @@ class region_model
void check_for_poison (tree expr, region_model_context *ctxt);
void on_assignment (const gassign *stmt, region_model_context *ctxt);
void on_call_pre (const gcall *stmt, region_model_context *ctxt);
void on_call_post (const gcall *stmt, region_model_context *ctxt);
bool on_call_pre (const gcall *stmt, region_model_context *ctxt);
void on_call_post (const gcall *stmt,
bool unknown_side_effects,
region_model_context *ctxt);
void handle_unrecognized_call (const gcall *call,
region_model_context *ctxt);
void on_return (const greturn *stmt, region_model_context *ctxt);
void on_setjmp (const gcall *stmt, const exploded_node *enode,
region_model_context *ctxt);
......@@ -1827,6 +1831,10 @@ class region_model_context
to ptrs becoming known to be NULL or non-NULL, rather than just
"unchecked") */
virtual void on_condition (tree lhs, enum tree_code op, tree rhs) = 0;
/* Hooks for clients to be notified when an unknown change happens
to SID (in response to a call to an unknown function). */
virtual void on_unknown_change (svalue_id sid) = 0;
};
/* A bundle of data for use when attempting to merge two region_model
......@@ -1993,6 +2001,10 @@ public:
{
}
void on_unknown_change (svalue_id sid ATTRIBUTE_UNUSED) FINAL OVERRIDE
{
}
private:
/* Implicitly delete any diagnostics in the dtor. */
auto_delete_vec<pending_diagnostic> m_diagnostics;
......
2020-01-14 David Malcolm <dmalcolm@redhat.com>
* gcc.dg/analyzer/data-model-1.c: Remove xfail.
* gcc.dg/analyzer/data-model-5b.c: Likewise.
* gcc.dg/analyzer/data-model-5c.c: Likewise.
* gcc.dg/analyzer/setjmp-3.c: Mark "foo" as pure.
* gcc.dg/analyzer/setjmp-4.c: Likewise.
* gcc.dg/analyzer/setjmp-6.c: Likewise.
* gcc.dg/analyzer/setjmp-7.c: Likewise.
* gcc.dg/analyzer/setjmp-7a.c: Likewise.
* gcc.dg/analyzer/setjmp-8.c: Likewise.
* gcc.dg/analyzer/setjmp-9.c: Likewise.
* gcc.dg/analyzer/unknown-fns.c: New test.
2020-01-14 David Malcolm <dmalcolm@redhat.com>
* gcc.dg/analyzer/CVE-2005-1689-dedupe-issue.c: New test.
2020-01-15 Jakub Jelinek <jakub@redhat.com>
......
......@@ -326,9 +326,7 @@ void test_16e (int i)
__analyzer_eval (j == i); /* { dg-warning "TRUE" } */
might_write_to (&j);
__analyzer_eval (j == i); /* { dg-warning "UNKNOWN" "" { xfail *-*-* } } */
/* { dg-warning "TRUE" "" { target *-*-* } .-1 } */
// TODO(xfail)
__analyzer_eval (j == i); /* { dg-warning "UNKNOWN" } */
}
/* TODO: and more complicated graph-like examples, where anything that's
......
......@@ -87,5 +87,4 @@ void test_1 (const char *str)
//__analyzer_dump();
if (obj)
unref (obj);
} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
// TODO (xfail): not sure why this is treated as leaking
}
......@@ -66,19 +66,13 @@ string_obj *new_string_obj (const char *str)
void unref (string_obj *obj)
{
//__analyzer_dump();
if (--obj->str_base.ob_refcnt == 0)
{
//__analyzer_dump();
free (obj);
}
free (obj);
}
void test_1 (const char *str)
{
string_obj *obj = new_string_obj (str);
//__analyzer_dump();
if (obj)
unref (obj);
} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
// TODO (xfail): not sure why this is treated as leaking
}
......@@ -5,7 +5,7 @@
#include <stddef.h>
#include "analyzer-decls.h"
extern void foo (int);
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf env;
......
......@@ -4,7 +4,7 @@
#include <setjmp.h>
#include "analyzer-decls.h"
extern int foo (int);
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf buf;
void inner (int x)
......
......@@ -2,7 +2,7 @@
#include <stddef.h>
#include <stdlib.h>
extern void foo (int);
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf env;
......
......@@ -2,7 +2,7 @@
#include <stddef.h>
#include <stdlib.h>
extern void foo (int);
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf env;
......
......@@ -4,7 +4,7 @@
#include <setjmp.h>
#include <stdlib.h>
extern void foo (int);
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf env;
......
......@@ -5,7 +5,7 @@
#include <stddef.h>
#include "analyzer-decls.h"
extern void foo (int);
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf env;
......
......@@ -5,7 +5,7 @@
#include <stddef.h>
#include "analyzer-decls.h"
extern void foo (int);
extern int foo (int) __attribute__ ((__pure__));
static jmp_buf env;
......
/* Verify that the analyzer correctly purges state when it sees a call to
an unknown function. */
#include <stdlib.h>
/* Verify fix for false-positive when checking for CVE-2005-1689. */
typedef struct _krb5_data {
char *data;
} krb5_data;
extern void krb5_read_message(krb5_data *buf);
void
test_1 (krb5_data inbuf)
{
free(inbuf.data);
krb5_read_message(&inbuf);
free(inbuf.data); /* { dg-bogus "double-'free'" } */
}
/* Verify that __pure__ functions are treated as not having side-effects. */
extern int called_by_test_1a (void *)
__attribute__ ((__pure__));
void test_1a (krb5_data inbuf)
{
free (inbuf.data);
called_by_test_1a (&inbuf);
free (inbuf.data); /* { dg-warning "double-'free'" } */
}
/* Verify that global pointers can be affected by an unknown function. */
void *global_ptr;
extern void unknown_side_effects (void);
void test_2 (void)
{
free (global_ptr);
unknown_side_effects ();
free (global_ptr);
}
extern void called_by_test_3 (void *);
void test_3a (void)
{
void *ptr = malloc (1024);
called_by_test_3 (ptr);
} /* { dg-bogus "leak" } */
void test_3b (void)
{
krb5_data k;
k.data = malloc (1024);
called_by_test_3 (&k);
} /* { dg-bogus "leak" } */
/* Verify that we traverse the graph of regions that are reachable from
the call. */
struct foo
{
struct foo *next;
int *ptr;
};
/* First, without a call to an unknown function. */
void test_4a (void)
{
struct foo node_a;
struct foo node_b;
node_a.next = &node_b;
node_b.ptr = malloc (sizeof (int));
global_ptr = &node_a;
*node_b.ptr = 42; /* { dg-warning "possibly-NULL" } */
/* { dg-warning "leak" "" { target *-*-* } .-1 } */
/* FIXME: the above leak report is correct, but is reported at the wrong
location. */
} /* { dg-warning "leak" } */
/* With a call to an unknown function. */
void test_4b (void)
{
struct foo node_a;
struct foo node_b;
node_a.next = &node_b;
node_b.ptr = malloc (sizeof (int));
global_ptr = &node_a;
unknown_side_effects (); /* everything potentially visible through global_ptr. */
*node_b.ptr = 42; /* { dg-bogus "possibly-NULL" } */
} /* { dg-bogus "leak" } */
extern void called_by_test_5 (const char *);
void test_5 (void)
{
called_by_test_5 ("???");
}
extern void called_by_test_6 (const struct foo *);
void test_6 (void)
{
struct foo node;
node.next = NULL;
node.ptr = malloc (sizeof (int));
/* This is a const ptr, but struct foo's ptr is non-const,
so we ought to assume it could be written to. */
called_by_test_6 (&node);
} /* { dg-bogus "leak" } */
/* TODO: things reachable from "outside" i.e. by params to caller to entrypoint. */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment