On 6/14/19 2:59 PM, Jeff Law wrote:
On 6/4/19 1:40 PM, Martin Sebor wrote:
On 6/3/19 5:24 PM, Martin Sebor wrote:
On 5/31/19 2:46 PM, Jeff Law wrote:
On 5/22/19 3:34 PM, Martin Sebor wrote:
-Wreturn-local-addr detects a subset of instances of returning
the address of a local object from a function but the warning
doesn't try to handle alloca or VLAs, or some non-trivial cases
of ordinary automatic variables[1].
The attached patch extends the implementation of the warning to
detect those. It still doesn't detect instances where the address
is the result of a built-in such strcpy[2].
Tested on x86_64-linux.
Martin
[1] For example, this is only diagnosed with the patch:
void* f (int i)
{
struct S { int a[2]; } s[2];
return &s->a[i];
}
[2] The following is not diagnosed even with the patch:
void sink (void*);
void* f (int i)
{
char a[6];
char *p = __builtin_strcpy (a, "123");
sink (p);
return p;
}
I would expect detecting to be possible and useful. Maybe as
a follow-up.
gcc-71924.diff
PR middle-end/71924 - missing -Wreturn-local-addr returning alloca
result
PR middle-end/90549 - missing -Wreturn-local-addr maybe returning an
address of a local array plus offset
gcc/ChangeLog:
PR c/71924
* gimple-ssa-isolate-paths.c (is_addr_local): New function.
(warn_return_addr_local_phi_arg, warn_return_addr_local): Same.
(find_implicit_erroneous_behavior): Call
warn_return_addr_local_phi_arg.
(find_explicit_erroneous_behavior): Call warn_return_addr_local.
gcc/testsuite/ChangeLog:
PR c/71924
* gcc.dg/Wreturn-local-addr-2.c: New test.
* gcc.dg/Walloca-4.c: Prune expected warnings.
* gcc.dg/pr41551.c: Same.
* gcc.dg/pr59523.c: Same.
* gcc.dg/tree-ssa/pr88775-2.c: Same.
* gcc.dg/winline-7.c: Same.
diff --git a/gcc/gimple-ssa-isolate-paths.c
b/gcc/gimple-ssa-isolate-paths.c
index 33fe352bb23..2933ecf502e 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -341,6 +341,135 @@ stmt_uses_0_or_null_in_undefined_way (gimple
*stmt)
return false;
}
+/* Return true if EXPR is a expression of pointer type that refers
+ to the address of a variable with automatic storage duration.
+ If so, set *PLOC to the location of the object or the call that
+ allocated it (for alloca and VLAs). When PMAYBE is non-null,
+ also consider PHI statements and set *PMAYBE when some but not
+ all arguments of such statements refer to local variables, and
+ to clear it otherwise. */
+
+static bool
+is_addr_local (tree exp, location_t *ploc, bool *pmaybe = NULL,
+ hash_set<gphi *> *visited = NULL)
+{
+ if (TREE_CODE (exp) == SSA_NAME)
+ {
+ gimple *def_stmt = SSA_NAME_DEF_STMT (exp);
+ enum gimple_code code = gimple_code (def_stmt);
+
+ if (is_gimple_assign (def_stmt))
+ {
+ tree type = TREE_TYPE (gimple_assign_lhs (def_stmt));
+ if (POINTER_TYPE_P (type))
+ {
+ tree ptr = gimple_assign_rhs1 (def_stmt);
+ return is_addr_local (ptr, ploc, pmaybe, visited);
+ }
+ return false;
+ }
So this is going to recurse on the rhs1 of something like
POINTER_PLUS_EXPR, that's a good thing :-) But isn't it non-selective
about the codes where we recurse?
Consider
ptr = (cond) ? res1 : res2
I think we'll end up recursing on the condition rather than looking at
res1 and res2.
I suspect there are a very limited number of expression codes that
appear on the RHS where we'd want to recurse on one or both operands.
POINTER_PLUS_EXPR, NOP_EXPR, maybe COND_EXPR (where you have to recurse
on both and logically and the result), BIT_AND (maybe we masked off some
bits in an address). That's probably about it :-)
Are there any other codes you've seen or think would be useful in
practice to recurse through? I'd rather list them explicitly rather
than just recurse down through every rhs1 we encounter.
I don't have a list of codes to test for. I initially contemplated
enumerating them but in the end decided the pointer type check would
be sufficient. I wouldn't expect a COND_EXPR here. Don't they get
transformed into PHIs? In all my tests they do and and running
the whole test suite with an assert that it doesn't come up doesn't
expose any either. (I left the assert for COND_EXPR there.) If
a COND_EXPR really can come up in a GIMPLE assignment here can you
please show me how so I can add a test for it?
A COND_EXPR on the RHS of an assignment is valid gimple. That's what we
need to consider here -- what is and what is not valid gimple. And its
more likely that PHIs will be transformed into RHS COND_EXPRs -- that's
standard practice for if-conversion.
Gosh, how to get one? It happens all the time :-) Since I know DOM so
well, I just shoved a quick assert into optimize_stmt to abort if we
encounter a gimple assignment where the RHS is a COND_EXPR. It blew up
instantly building libgcc :-)
COmpile the attached code with -O2 -m32.
I wasn't saying it's not valid Gimple, just that I hadn't seen it
come up here despite compiling Glibc and the kernel with the patched
GCC. The only codes I saw are these:
addr_expr, array_ref, bit_and_expr, component_ref, max_expr,
mem_ref, nop_expr, parm_decl, pointer_plus_expr, ssa_name,
and var_decl
What I was asking for is a test case that makes COND_EXPR come up
in this context. But I managed to find one by triggering the ICE
with GDB. The file reduced to the following test case:
extern struct S s; // S has to be an incomplete type
void* f (int n)
{
void *p;
int x = 0;
for (int i = n; i >= 0; i--)
{
p = &s;
if (p == (void*)-1)
x = 1;
else if (p)
return p;
}
return x ? (void*)-1 : 0;
}
and the dump:
<bb 6> [local count: 59055800]:
# x_10 = PHI <1(5), 0(2)>
_5 = x_10 != 0 ? -1B : 0B;
<bb 7> [local count: 114863532]:
# _3 = PHI <&s(4), _5(6), &s(3)>
return _3;
It seems a little odd that the COND_EXPR disappears when either
of the constant addresses becomes the address of an object (or
the result of alloca), and also when the number of iterations
of the loop is constant. That's probably why it so rarely comes
up in this context.
That said, even though I've seen a few references to COND_EXPR
in the midle-end I have been assuming that they, in general, do
get transformed into PHIs. But checking the internals manual
I found in Section 12.6.3 this:
A C ?: expression is converted into an if statement with each
branch assigning to the same temporary. ... The GIMPLE level
if-conversion pass re-introduces ?: expression, if appropriate.
It is used to vectorize loops with conditions using vector
conditional operations.
This GDB test case is likely the result of this reintroduction.
You'll see them for stuff like this:
;; basic block 24, loop depth 0
;; pred: 23
_244 = _1 == 0 ? 1 : 2;
In a few spots.
In addition to COND_EXPR, I'd be worried about VEC_COND_EXPR,
VEC_PERM_EXPR and a host of others.
And in a more general sense, this kind of permissiveness is not future
proof. What happens if someone needs to add another EXPR node that is
valid on the RHS where such recursion is undesirable? How are they
supposed to know that we've got this permissive recursive call and that
it's going to do the wrong thing? And if it's an EXPR node with no
arguments, then we're going to do a read out of the bounds of the object
and all bets are off at that point (yes we have zero operand EXPR nodes,
but thankfully I don't think they should up in the contexts you care about).
Sure. When things are hardcoded this way the same argument can
be made both ways. If we just handle A and B and then someone
adds an X that matches one of the two, it won't be handled. Either
way, some work is necessary to deal with the new Z. One way to
avoid it would be by providing an API to query this property of
a code (e.g., a function like int gimple_result_aliases_operand
(gimple *stmt, int opno)).
I'd be much more comfortable with a tight predicate controlling recursion.
I've added ADDR_EXPR, COND_EXPR, MAX_EXPR, MIN_EXPR, NOP_EXPR, and
POINTER_PLUS_EXPR based on the survey I did. I don't have tests for
the rest so I've left them out for now. If/when I come up with them
I'll add them.
+
+ if (code == GIMPLE_PHI && pmaybe)
+ {
+ unsigned count = 0;
+ gphi *phi_stmt = as_a <gphi *> (def_stmt);
+
+ unsigned nargs = gimple_phi_num_args (phi_stmt);
+ for (unsigned i = 0; i < nargs; ++i)
+ {
+ if (!visited->add (phi_stmt))
+ {
+ tree arg = gimple_phi_arg_def (phi_stmt, i);
+ if (is_addr_local (arg, ploc, pmaybe, visited))
+ ++count;
+ }
+ }
So imagine
p = phi (x1, x1, x2)
Where both x1 and x2 would pass is_addr_local. I think this code would
incorrectly set pmaybe.
I suppose it would but this happens readily with or without my
patch, for example here:
But those seem like distinct issues. The one I pointed out looks like a
pretty simple case to handle. It's just a logic error.
It wasn't a logic error. I simply didn't think it was necessary
to worry about the accuracy of an inherently inaccurate warning,
and I didn't want make more intrusive changes than was called for
by my simple fix/enhancement. I was also keeping in mind your
preference for smaller change sets. But since you insist I went
ahead and improved things. The warning is all the better for it,
and the changes I made exposed a number of other bugs in GCC.
At the same time, it seems that the bar for a simple bug fixes
and small enhancements should not be in excess of what's possible
by the original design.
Anyway, attached is an updated revision with the recursion limited
to just the codes you mentioned, and hopefully with an acceptable
accuracy. The patch depends on a fix for the hash_map bug 90923
that I just posted:
https://gcc.gnu.org/ml/gcc-patches/2019-06/msg01105.html
Martin
int* f (int i)
{
int a[2], b[2];
int *p = i ? a : b;
return p;
}
We end up with two instances of "function may return address
of local variable," one for each local, because
find_implicit_erroneous_behavior only issues the "may return"
kind of warning.
This looks more like a failing of the core approach in the erroneous
path code.
I suspect that no solution will be perfect, at a minimum because
multiple return statements sometimes get merged into one, so what
in the source code is a single return that definitely returns
the address a local may end up merged with one that returns
a null (and triggering a maybe kind of warning). I have also
seen warnings in some non-trivial test cases that suggest that
some return statements get split up into two where a "may return"
could turn into a "definitely does return."
Certainly. I don't expect any solution will be perfect here, but I
think fixing the logic error in the noted loop would help
It also seems to me that you can break the loop as soon as you've got a
nonzero count and get a false return from is_addr_local. Not sure if
that'll matter in practice or not.
This is only possible if we're willing to lose some detail (i.e.,
if we are willing to only point to one variable when returning
the address of two or more). I don't find that very user-friendly.
ACK. Ignore that comment then.
Jeff
PR middle-end/71924 - missing -Wreturn-local-addr returning alloca result
PR middle-end/90549 - missing -Wreturn-local-addr maybe returning an address of a local array plus offset
gcc/ChangeLog:
PR middle-end/71924
PR middle-end/90549
* gimple-ssa-isolate-paths.c (isolate_path): Add attribute.
(args_loc_t): New type.
(args_loc_t, locmap_t): same.
(diag_returned_locals): New function.
(is_addr_local): Same.
(handle_return_addr_local_phi_arg, warn_return_addr_local): Same.
(find_implicit_erroneous_behavior): Call warn_return_addr_local_phi_arg.
(find_explicit_erroneous_behavior): Call warn_return_addr_local.
libgcc/ChangeLog:
* generic-morestack.c: Disable -fisolate-erroneous-paths-dereference
to get around PR libgcc/90918.
gcc/testsuite/ChangeLog:
PR middle-end/71924
PR middle-end/90549
* gcc.dg/Wreturn-local-addr-2.c: New test.
* gcc.dg/Wreturn-local-addr-4.c: New test.
* gcc.dg/Wreturn-local-addr-5.c: New test.
* gcc.dg/Wreturn-local-addr-6.c: New test.
* gcc.dg/Wreturn-local-addr-7.c: New test.
* gcc.dg/Wreturn-local-addr-8.c: New test.
* gcc.dg/Walloca-4.c: Prune expected warnings.
* gcc.dg/pr41551.c: Same.
* gcc.dg/pr59523.c: Same.
* gcc.dg/tree-ssa/pr88775-2.c: Same.
* gcc.dg/winline-7.c: Same.
diff --git a/gcc/gimple-ssa-isolate-paths.c b/gcc/gimple-ssa-isolate-paths.c
index 33fe352bb23..13b1f5f2349 100644
--- a/gcc/gimple-ssa-isolate-paths.c
+++ b/gcc/gimple-ssa-isolate-paths.c
@@ -130,7 +130,7 @@ insert_trap (gimple_stmt_iterator *si_p, tree op)
Return BB'. */
-basic_block
+ATTRIBUTE_RETURNS_NONNULL basic_block
isolate_path (basic_block bb, basic_block duplicate,
edge e, gimple *stmt, tree op, bool ret_zero)
{
@@ -341,6 +341,283 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
return false;
}
+/* Describes the property of a return statement that may return
+ the address of one or more local variables. */
+struct args_loc_t
+{
+ args_loc_t (): nargs (), locvec (), ptr (&ptr)
+ {
+ locvec.create (4);
+ }
+
+ args_loc_t (const args_loc_t &rhs)
+ : nargs (rhs.nargs), locvec (rhs.locvec.copy ()), ptr (&ptr) { }
+
+ args_loc_t& operator= (const args_loc_t &rhs)
+ {
+ nargs = rhs.nargs;
+ locvec.release ();
+ locvec = rhs.locvec.copy ();
+ return *this;
+ }
+
+ ~args_loc_t ()
+ {
+ locvec.release ();
+ gcc_assert (ptr == &ptr);
+ }
+
+ /* For a PHI in a return statement its number of arguments. When less
+ than LOCVEC.LENGTH () implies that an address of local may but need
+ not be returned by the statement. Otherwise it implies it definitely
+ is returned. Used to issue "may be" vs "is" diagnostics. */
+ unsigned nargs;
+ /* The locations of local variables/alloca calls returned by the return
+ statement. Avoid using auto_vec here since it's not safe to copy due
+ to pr90904. */
+ vec <location_t> locvec;
+ void *ptr;
+};
+
+/* A mapping from a return statement to the locations of local variables
+ whose addresses it may return. */
+typedef hash_map <gimple *, args_loc_t> locmap_t;
+
+/* Given the LOCMAP mapping, issue diagnostics about returning addresses
+ of local variables. When MAYBE is set, all diagnostics will be of
+ the "may return" kind. Otherwise each will be determined based on
+ the equality of the corresponding NARGS and LOCVEC.LENGTH () values. */
+
+static void
+diag_returned_locals (bool maybe, const locmap_t &locmap)
+{
+ for (locmap_t::iterator it = locmap.begin (); it != locmap.end (); ++it)
+ {
+ gimple *stmt = (*it).first;
+ const args_loc_t &argsloc = (*it).second;
+ location_t stmtloc = gimple_location (stmt);
+
+ auto_diagnostic_group d;
+ unsigned nargs = argsloc.locvec.length ();
+ if (warning_at (stmtloc, OPT_Wreturn_local_addr,
+ (maybe || argsloc.nargs > nargs
+ ? G_("function may return address of local variable")
+ : G_("function returns address of local variable"))))
+ {
+ for (unsigned i = 0; i != nargs; ++i)
+ inform (argsloc.locvec[i], "declared here");
+ }
+ }
+}
+
+/* Return true if EXPR is a expression of pointer type that refers
+ to the address of a variable with automatic storage duration.
+ If so, add an entry to *PLOCMAP and insert INTO PLOCMAP->LOCVEC
+ the locations of the corresponding local variables whose address
+ is returned by the statement. VISITED is a bitmap of PHI nodes
+ already visited by recursive calls. */
+
+static bool
+is_addr_local (gimple *return_stmt, tree exp, locmap_t *plocmap,
+ hash_set<gphi *> *visited)
+{
+ if (TREE_CODE (exp) == ADDR_EXPR)
+ {
+ tree baseaddr = get_base_address (TREE_OPERAND (exp, 0));
+ if (TREE_CODE (baseaddr) == MEM_REF)
+ return is_addr_local (return_stmt, TREE_OPERAND (baseaddr, 0),
+ plocmap, visited);
+
+ if ((!VAR_P (baseaddr)
+ || is_global_var (baseaddr))
+ && TREE_CODE (baseaddr) != PARM_DECL)
+ return false;
+
+ args_loc_t &argsloc = plocmap->get_or_insert (return_stmt);
+ argsloc.locvec.safe_push (DECL_SOURCE_LOCATION (baseaddr));
+ return true;
+ }
+
+ if (!POINTER_TYPE_P (TREE_TYPE (exp)))
+ return false;
+
+ if (TREE_CODE (exp) == SSA_NAME)
+ {
+ gimple *def_stmt = SSA_NAME_DEF_STMT (exp);
+ enum gimple_code code = gimple_code (def_stmt);
+
+ if (is_gimple_assign (def_stmt))
+ {
+ tree type = TREE_TYPE (gimple_assign_lhs (def_stmt));
+ if (POINTER_TYPE_P (type))
+ {
+ tree_code code = gimple_assign_rhs_code (def_stmt);
+ tree ptr1 = NULL_TREE, ptr2 = NULL_TREE;
+ if (code == COND_EXPR)
+ {
+ ptr1 = gimple_assign_rhs2 (def_stmt);
+ ptr2 = gimple_assign_rhs3 (def_stmt);
+ }
+ else if (code == MAX_EXPR || code == MIN_EXPR)
+ {
+ ptr1 = gimple_assign_rhs1 (def_stmt);
+ ptr2 = gimple_assign_rhs2 (def_stmt);
+ }
+ else if (code == ADDR_EXPR
+ || code == NOP_EXPR
+ || code == POINTER_PLUS_EXPR)
+ ptr1 = gimple_assign_rhs1 (def_stmt);
+
+ /* Avoid short-circuiting the logical OR result in case
+ both oerands refer to local variables, in which case
+ both should be identified in the warning. */
+ bool res1 = false, res2 = false;
+ if (ptr1)
+ res1 = is_addr_local (return_stmt, ptr1, plocmap, visited);
+ if (ptr2)
+ res2 = is_addr_local (return_stmt, ptr2, plocmap, visited);
+ return res1 || res2;
+ }
+ return false;
+ }
+
+ if (code == GIMPLE_CALL
+ && gimple_call_builtin_p (def_stmt))
+ {
+ tree fn = gimple_call_fndecl (def_stmt);
+ int code = DECL_FUNCTION_CODE (fn);
+ if (code == BUILT_IN_ALLOCA
+ || code == BUILT_IN_ALLOCA_WITH_ALIGN
+ || code == BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX)
+ {
+ args_loc_t &argsloc = plocmap->get_or_insert (return_stmt);
+ argsloc.locvec.safe_push (gimple_location (def_stmt));
+ return true;
+ }
+
+ if (gimple_call_num_args (def_stmt) < 1)
+ return false;
+
+ switch (code)
+ {
+ case BUILT_IN_MEMCPY:
+ case BUILT_IN_MEMCPY_CHK:
+ case BUILT_IN_MEMPCPY:
+ case BUILT_IN_MEMPCPY_CHK:
+ case BUILT_IN_MEMMOVE:
+ case BUILT_IN_MEMMOVE_CHK:
+ case BUILT_IN_STPCPY:
+ case BUILT_IN_STPCPY_CHK:
+ case BUILT_IN_STPNCPY:
+ case BUILT_IN_STPNCPY_CHK:
+ case BUILT_IN_STRCAT:
+ case BUILT_IN_STRCAT_CHK:
+ case BUILT_IN_STRCHR:
+ case BUILT_IN_STRCPY:
+ case BUILT_IN_STRCPY_CHK:
+ case BUILT_IN_STRNCAT:
+ case BUILT_IN_STRNCAT_CHK:
+ case BUILT_IN_STRNCPY:
+ case BUILT_IN_STRNCPY_CHK:
+ case BUILT_IN_STRRCHR:
+ case BUILT_IN_STRSTR:
+ return is_addr_local (return_stmt,
+ gimple_call_arg (def_stmt, 0),
+ plocmap, visited);
+ default:
+ return false;
+ }
+ }
+
+ if (code == GIMPLE_PHI && visited)
+ {
+ gphi *phi_stmt = as_a <gphi *> (def_stmt);
+ if (visited->add (phi_stmt))
+ return false;
+
+ unsigned count = 0;
+ unsigned nargs = gimple_phi_num_args (phi_stmt);
+ args_loc_t &argsloc = plocmap->get_or_insert (return_stmt);
+ if (argsloc.nargs < nargs)
+ argsloc.nargs = nargs;
+ for (unsigned i = 0; i < gimple_phi_num_args (phi_stmt); ++i)
+ {
+ tree arg = gimple_phi_arg_def (phi_stmt, i);
+ if (is_addr_local (return_stmt, arg, plocmap, visited))
+ ++count;
+ }
+ return count != 0;
+ }
+ }
+
+ return false;
+}
+
+/* Detect returning the address of a local variable in a PHI result LHS
+ and argument ARG and PHI edge E in basic block BB. Add an entry for
+ each use to LOCMAP, setting its NARGS member to the NARGS argument.
+ Call isolate_path for each returned address.
+ Return DUPLICATE on success and NULL when no such address was found. */
+
+static basic_block
+handle_return_addr_local_phi_arg (basic_block bb, basic_block duplicate,
+ tree lhs, tree arg, edge e, locmap_t &locmap,
+ unsigned nargs)
+{
+ hash_set<gphi *> visited_phis;
+ /* Use (gimple*)-1 as a temporary placeholder and replace it with
+ the return statement below once it is known. Using a null doesn't
+ work because it's used by the hash_map to mean no-entry. */
+ if (!is_addr_local ((gimple*)-1, arg, &locmap, &visited_phis))
+ {
+ /* Remove the placeholder regardless of success or failure. */
+ locmap.remove ((gimple*)-1);
+ return NULL;
+ }
+
+ const args_loc_t* const placeargsloc = locmap.get ((gimple*)-1);
+ const unsigned nlocs = placeargsloc->locvec.length ();
+ gcc_assert (nlocs);
+
+ gimple *use_stmt;
+ imm_use_iterator iter;
+
+ /* Look for uses of the PHI result LHS in return statements. */
+ FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs)
+ {
+ greturn *return_stmt = dyn_cast <greturn *> (use_stmt);
+ if (!return_stmt)
+ continue;
+
+ if (gimple_return_retval (return_stmt) != lhs)
+ continue;
+
+ /* Add an entry for the return statement and the locations
+ oof the PHI arguments obtaine above to the map. */
+ args_loc_t &argsloc = locmap.get_or_insert (use_stmt);
+ argsloc.nargs = nargs;
+ unsigned nelts = argsloc.locvec.length () + nlocs;
+ argsloc.locvec.reserve (nelts);
+ argsloc.locvec.splice (placeargsloc->locvec);
+
+ if ((flag_isolate_erroneous_paths_dereference
+ || flag_isolate_erroneous_paths_attribute)
+ && gimple_bb (use_stmt) == bb)
+ {
+ duplicate = isolate_path (bb, duplicate, e,
+ use_stmt, lhs, true);
+
+ /* When we remove an incoming edge, we need to
+ reprocess the Ith element. */
+ cfg_altered = true;
+ }
+ }
+
+ locmap.remove ((gimple*)-1);
+
+ return duplicate;
+}
+
/* Look for PHI nodes which feed statements in the same block where
the value of the PHI node implies the statement is erroneous.
@@ -352,6 +629,8 @@ stmt_uses_0_or_null_in_undefined_way (gimple *stmt)
static void
find_implicit_erroneous_behavior (void)
{
+ locmap_t locmap;
+
basic_block bb;
FOR_EACH_BB_FN (bb, cfun)
@@ -388,6 +667,12 @@ find_implicit_erroneous_behavior (void)
gphi *phi = si.phi ();
tree lhs = gimple_phi_result (phi);
+ /* Initial number of PHI arguments. The result may change
+ from one iteration of the loop below to the next in
+ response to changes to the CFG but only the initial
+ value is stored below for use by diagnostics. */
+ unsigned nargs = gimple_phi_num_args (phi);
+
/* PHI produces a pointer result. See if any of the PHI's
arguments are NULL.
@@ -395,63 +680,24 @@ find_implicit_erroneous_behavior (void)
index, hence the ugly way we update I for each iteration. */
basic_block duplicate = NULL;
for (unsigned i = 0, next_i = 0;
- i < gimple_phi_num_args (phi);
- i = next_i)
+ i < gimple_phi_num_args (phi); i = next_i)
{
- tree op = gimple_phi_arg_def (phi, i);
+ tree arg = gimple_phi_arg_def (phi, i);
edge e = gimple_phi_arg_edge (phi, i);
- imm_use_iterator iter;
- gimple *use_stmt;
- next_i = i + 1;
+ duplicate = handle_return_addr_local_phi_arg (bb, duplicate, lhs,
+ arg, e, locmap,
+ nargs);
+ next_i = duplicate ? i : i + 1;
- if (TREE_CODE (op) == ADDR_EXPR)
- {
- tree valbase = get_base_address (TREE_OPERAND (op, 0));
- if ((VAR_P (valbase) && !is_global_var (valbase))
- || TREE_CODE (valbase) == PARM_DECL)
- {
- FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs)
- {
- greturn *return_stmt
- = dyn_cast <greturn *> (use_stmt);
- if (!return_stmt)
- continue;
-
- if (gimple_return_retval (return_stmt) != lhs)
- continue;
-
- {
- auto_diagnostic_group d;
- if (warning_at (gimple_location (use_stmt),
- OPT_Wreturn_local_addr,
- "function may return address "
- "of local variable"))
- inform (DECL_SOURCE_LOCATION(valbase),
- "declared here");
- }
-
- if ((flag_isolate_erroneous_paths_dereference
- || flag_isolate_erroneous_paths_attribute)
- && gimple_bb (use_stmt) == bb)
- {
- duplicate = isolate_path (bb, duplicate, e,
- use_stmt, lhs, true);
-
- /* When we remove an incoming edge, we need to
- reprocess the Ith element. */
- next_i = i;
- cfg_altered = true;
- }
- }
- }
- }
-
- if (!integer_zerop (op))
+ if (!integer_zerop (arg))
continue;
location_t phi_arg_loc = gimple_phi_arg_location (phi, i);
+ imm_use_iterator iter;
+ gimple *use_stmt;
+
/* We've got a NULL PHI argument. Now see if the
PHI's result is dereferenced within BB. */
FOR_EACH_IMM_USE_STMT (use_stmt, iter, lhs)
@@ -480,6 +726,49 @@ find_implicit_erroneous_behavior (void)
}
}
}
+
+ diag_returned_locals (false, locmap);
+}
+
+/* Detect and diagnose returning the address of a local variable
+ in RETURN_STMT in basic block BB. This only becomes undefined
+ behavior if the result is used, so we do not insert a trap and
+ only return NULL instead. */
+
+static void
+warn_return_addr_local (basic_block bb, greturn *return_stmt)
+{
+ tree val = gimple_return_retval (return_stmt);
+ if (!val)
+ return;
+
+ locmap_t locmap;
+ hash_set<gphi *> visited_phis;
+ if (!is_addr_local (return_stmt, val, &locmap, &visited_phis))
+ return;
+
+ /* We only need it for this particular case. */
+ calculate_dominance_info (CDI_POST_DOMINATORS);
+
+ const args_loc_t *argsloc = locmap.get (return_stmt);
+ gcc_assert (argsloc);
+
+ bool maybe = argsloc->nargs > argsloc->locvec.length ();
+ if (!maybe)
+ maybe = !dominated_by_p (CDI_POST_DOMINATORS,
+ single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)), bb);
+
+ diag_returned_locals (maybe, locmap);
+
+ /* Do not modify code if the user only asked for
+ warnings. */
+ if (flag_isolate_erroneous_paths_dereference
+ || flag_isolate_erroneous_paths_attribute)
+ {
+ tree zero = build_zero_cst (TREE_TYPE (val));
+ gimple_return_set_retval (return_stmt, zero);
+ update_stmt (return_stmt);
+ }
}
/* Look for statements which exhibit erroneous behavior. For example
@@ -525,49 +814,10 @@ find_explicit_erroneous_behavior (void)
break;
}
- /* Detect returning the address of a local variable. This only
- becomes undefined behavior if the result is used, so we do not
- insert a trap and only return NULL instead. */
+ /* Look for a return statement that returns the address
+ of a local variable or the result of alloca. */
if (greturn *return_stmt = dyn_cast <greturn *> (stmt))
- {
- tree val = gimple_return_retval (return_stmt);
- if (val && TREE_CODE (val) == ADDR_EXPR)
- {
- tree valbase = get_base_address (TREE_OPERAND (val, 0));
- if ((VAR_P (valbase) && !is_global_var (valbase))
- || TREE_CODE (valbase) == PARM_DECL)
- {
- /* We only need it for this particular case. */
- calculate_dominance_info (CDI_POST_DOMINATORS);
- const char* msg;
- bool always_executed = dominated_by_p
- (CDI_POST_DOMINATORS,
- single_succ (ENTRY_BLOCK_PTR_FOR_FN (cfun)), bb);
- if (always_executed)
- msg = N_("function returns address of local variable");
- else
- msg = N_("function may return address of "
- "local variable");
- {
- auto_diagnostic_group d;
- if (warning_at (gimple_location (stmt),
- OPT_Wreturn_local_addr, msg))
- inform (DECL_SOURCE_LOCATION(valbase),
- "declared here");
- }
-
- /* Do not modify code if the user only asked for
- warnings. */
- if (flag_isolate_erroneous_paths_dereference
- || flag_isolate_erroneous_paths_attribute)
- {
- tree zero = build_zero_cst (TREE_TYPE (val));
- gimple_return_set_retval (return_stmt, zero);
- update_stmt (stmt);
- }
- }
- }
- }
+ warn_return_addr_local (bb, return_stmt);
}
}
}
diff --git a/gcc/testsuite/gcc.dg/Walloca-4.c b/gcc/testsuite/gcc.dg/Walloca-4.c
index 85dcb7b9bb9..1fbed597b98 100644
--- a/gcc/testsuite/gcc.dg/Walloca-4.c
+++ b/gcc/testsuite/gcc.dg/Walloca-4.c
@@ -7,11 +7,12 @@
{
char *src;
- _Bool
- use_alloca = (((rear_ptr - w) * sizeof (char)) < 4096U);
- if (use_alloca)
+ _Bool use_alloca = (((rear_ptr - w) * sizeof (char)) < 4096U);
+ if (use_alloca)
src = (char *) __builtin_alloca ((rear_ptr - w) * sizeof (char));
else
src = (char *) __builtin_malloc ((rear_ptr - w) * sizeof (char));
return src;
}
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-2.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-2.c
new file mode 100644
index 00000000000..0e3435c8256
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-2.c
@@ -0,0 +1,293 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+ { dg-do compile }
+ { dg-options "-O2 -Wall" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+struct A { int a, b, c; };
+struct B { int a, b, c[]; };
+
+void sink (void*, ...);
+
+ATTR (noipa) void*
+return_alloca (int n)
+{
+ void *p = __builtin_alloca (n);
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_index_cst (int n)
+{
+ int *p = (int*)__builtin_alloca (n);
+ p = &p[1];
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_plus_cst (int n)
+{
+ int *p = (int*)__builtin_alloca (n);
+ p += 1;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_plus_var (int n, int i)
+{
+ char *p = (char*)__builtin_alloca (n);
+ p += i;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_member_1 (int n)
+{
+ struct A *p = (struct A*)__builtin_alloca (n);
+ sink (&p->a);
+ return &p->a; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_member_2 (int n)
+{
+ struct A *p = (struct A*)__builtin_alloca (n);
+ sink (&p->b);
+ return &p->b; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_flexarray (int n)
+{
+ struct B *p = (struct B*)__builtin_alloca (n);
+ sink (p->c);
+ return p->c; /* { dg-warning "function returns address of local" } */
+}
+
+
+ATTR (noipa) void*
+return_array (void)
+{
+ int a[32];
+ void *p = a;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_index_cst (void)
+{
+ int a[32];
+ void *p = &a[2];
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_plus_cst (void)
+{
+ int a[32];
+ void *p = a + 2;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_plus_var (int i)
+{
+ int a[32];
+ void *p = a + i;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_member_1 (void)
+{
+ struct A a[2];
+ int *p = &a[1].a;
+ sink (a, p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_member_2 (void)
+{
+ struct A a[32];
+ int *p = &a[1].b;
+ sink (a, p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+
+ATTR (noipa) void*
+return_vla (int n)
+{
+ char a[n];
+ void *p = a;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_index_cst (int n)
+{
+ char a[n];
+ char *p = &a[3];
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_plus_cst (int n)
+{
+ char a[n];
+ char *p = a + 3;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_index_var (int n, int i)
+{
+ char a[n];
+ char *p = &a[i];
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_plus_var (int n, int i)
+{
+ char a[n];
+ char *p = a + i;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_member_1 (int n, int i)
+{
+ struct A a[n];
+ void *p = &a[i].a;
+ sink (a, p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_vla_member_2 (int n, int i)
+{
+ struct A a[n];
+ void *p = &a[i].b;
+ sink (a, p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+
+ATTR (noipa) void*
+return_alloca_or_alloca (int n, int i)
+{
+ void *p = i ? __builtin_alloca (n * i) : __builtin_alloca (n);
+ sink (p);
+ /* The warning here should really be "function returns". */
+ return p; /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_or_alloca_2 (int n, int i)
+{
+ void *p0 = __builtin_alloca (n);
+ void *p1 = __builtin_alloca (n * 2);
+ void *p = i ? p0 : p1;
+ sink (p0, p1, p);
+ /* Same as above. */
+ return p; /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_or_array (int i)
+{
+ int a[5];
+ int b[7];
+ void *p = i ? a : b;
+ sink (a, b, p);
+ /* The warning here should really be "function returns". */
+ return p; /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+ATTR (noipa) void*
+return_array_or_array_plus_var (int i, int j)
+{
+ int a[5];
+ int b[7];
+
+ void *p0 = a + i;
+ void *p1 = b + j;
+
+ void *p = i < j ? p0 : p1;
+ sink (a, b, p0, p1, p);
+ /* The warning here should really be "function returns". */
+ return p; /* { dg-warning "function (returns|may return) address of local" } */
+}
+
+extern int global[32];
+
+ATTR (noipa) void*
+may_return_global_or_alloca (int n, int i)
+{
+ void *p = i ? global : __builtin_alloca (n);
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+
+ATTR (noipa) void*
+may_return_global_or_alloca_plus_cst (int n, int i)
+{
+ int *p = i ? global : (int*)__builtin_alloca (n);
+ p += 7;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_array (int n, int i)
+{
+ int a[32];
+ void *p = i ? global : a;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_array_plus_cst (int n, int i)
+{
+ int a[32];
+ int *p = i ? global : a;
+ p += 4;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_vla (int n, int i)
+{
+ int a[n];
+ void *p = i ? global : a;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+may_return_global_or_vla_plus_cst (int n, int i)
+{
+ int a[n];
+ int *p = i ? global : a;
+ p += 4;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-3.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-3.c
new file mode 100644
index 00000000000..6dad7af97e6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-3.c
@@ -0,0 +1,248 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+ { dg-do compile }
+ { dg-options "-O2 -Wall" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+typedef __INTPTR_TYPE__ intptr_t;
+
+struct A { int a, b, c; };
+struct B { int a, b, c[]; };
+
+extern int g1[5], g2[5], g3[5], g4[5], g5[5];
+
+void sink (void*, ...);
+
+/* Verify that a pointer difference expression is handled correctly
+ even when converted to a pointer. */
+
+ATTR (noipa) void*
+return_local_diff_cst (void)
+{
+ int a[5];
+ void *p = (void*)(&a[4] - &a[1]);
+ return p;
+}
+
+ATTR (noipa) void*
+return_local_diff_var (int i, int j)
+{
+ int a[5];
+ void *p = (void*)(&a[j] - &a[i]);
+ return p;
+}
+
+ATTR (noipa) void*
+return_2_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ void *p = i < 0 ? a : b;
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+/* Verify that returning the address of a local converted to intptr_t
+ is not diagnosed (see bug 90737 for a case the front-end gets wrong). */
+
+ATTR (noipa) intptr_t
+return_int_2_locals (int i)
+{
+ int a[1];
+ int b[2];
+ void *p = i < 0 ? a : b;
+ return (intptr_t)p;
+}
+
+/* Verify that a conditional expression with a pointer first operand
+ is handled correctly. */
+
+ATTR (noipa) void*
+return_2_locals_ptrcond (void *q)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ void *p = q ? a : b;
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+/* Verify that a preincrement expression with a pointer operand is
+ handled correctly. */
+
+ATTR (noipa) void*
+return_2_locals_ptrinc (void *q)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int *p = q ? a : b;
+ return ++p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+
+ void *p = i < 0 ? a : 0 < i ? c : b;
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+/* Verify that a conditional expression with a pointer first operand
+ is handled correctly. */
+
+ATTR (noipa) void*
+return_3_locals_ptrcond (void *p, void *q)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+
+ void *r = q ? r ? a : b : c;
+ return r; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_5_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+ int d[4]; /* { dg-message "declared here" } */
+ int e[5]; /* { dg-message "declared here" } */
+
+ void *p = i < -1 ? a : i < 0 ? b : 1 < i ? e : 0 < i ? d : c;
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_1_global_4_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+ int d[4]; /* { dg-message "declared here" } */
+
+ void *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? d : c;
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+
+ void *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : c;
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ void *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : g3;
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_4_globals_1_local (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+
+ void *p = i < -1 ? a : i < 0 ? g1 : 1 < i ? g2 : 0 < i ? g4 : g3;
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_all_globals (int i)
+{
+ void *p = i < -1 ? g1 : i < 0 ? g2 : 1 < i ? g3 : 0 < i ? g5 : g4;
+ return p;
+}
+
+
+ATTR (noipa) void*
+return_2_alloca_local_cstoff (int n, int i)
+{
+ int *a = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int *b = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int *p = i < 0 ? a : b;
+ p += 1;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_local_cstoff (int n, int i)
+{
+ int *a = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int *p = i < 0 ? a : b;
+ p += 1;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_local_alloca_cstoff (int n, int i)
+{
+ int a[2]; /* { dg-message "declared here" } */
+ int *b = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int *p = i < 0 ? a : b;
+ p += 1;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_locals_cstoff (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int *p = i < 0 ? a : b;
+ p += 1;
+ sink (p);
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals_cstoff (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+
+ int *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : c;
+ p += 1;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_alloca_local_varoff (int n, int i, int j)
+{
+ int *a = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ int *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : g3;
+ p += j;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals_varoff (int i, int j)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ int *p = i < -1 ? a : i < 0 ? b : 1 < i ? g1 : 0 < i ? g2 : g3;
+ p += j;
+ sink (p);
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-4.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-4.c
new file mode 100644
index 00000000000..0a451efcaf0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-4.c
@@ -0,0 +1,370 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+ { dg-do compile }
+ { dg-options "-O2 -Wall" } */
+
+#define ATTR(...) __attribute__ ((__VA_ARGS__))
+
+struct A { int a, b, c; };
+struct B { int a, b, c[]; };
+
+extern int g1[5], g2[5], g3[5], g4[5], g5[5];
+
+void sink (void*, ...);
+
+ATTR (noipa) void*
+return_2_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ void *p = b;
+ if (i < 0)
+ p = a;
+
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_locals_after_2_globals (int i, int j)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ int *p;
+ if (i < 0)
+ p = g1;
+ else
+ p = g2;
+
+ sink (p);
+
+ if (j < 0)
+ p = a;
+ else
+ p = b;
+
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+
+ void *p = b + 1;
+ if (i < 0)
+ p = a;
+ else if (0 < i)
+ p = c + 2;
+
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_5_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+ int d[4]; /* { dg-message "declared here" } */
+ int e[5]; /* { dg-message "declared here" } */
+
+ void *p = &c[2];
+ if (i < -1)
+ p = a;
+ else if (i < 0)
+ p = &b[1];
+ else if (1 < i)
+ p = &e[4];
+ else if (0 < i)
+ p = &d[3];
+
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_5_locals_switch (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+ int d[4]; /* { dg-message "declared here" } */
+ int e[5]; /* { dg-message "declared here" } */
+
+ void *p = 0;
+
+ switch (i)
+ {
+ case 0: p = &a[1]; break;
+ case 1: p = &b[2]; break;
+ case 2: p = &c[3]; break;
+ case 3: p = &d[4]; break;
+ default: p = &e[5]; break;
+ }
+
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_1_global_4_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+ int d[4]; /* { dg-message "declared here" } */
+
+ void *p = c;
+ if (i < -1)
+ sink (p = a);
+ else if (i < 0)
+ sink (p = b);
+ else if (1 < i)
+ sink (p = g1);
+ else if (0 < i)
+ sink (p = d);
+
+ sink (p, a, b, c, d);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_1_global_4_locals_switch (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+ int d[4]; /* { dg-message "declared here" } */
+
+ void *p = 0;
+
+ switch (i)
+ {
+ case 0: p = &a[0]; break;
+ case 1: p = &b[1]; break;
+ case 2: p = &c[2]; break;
+ case 3: p = &d[3]; break;
+ }
+
+ sink (p);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+
+ void *p = c;
+ if (i < -1)
+ p = a;
+ else if (i < 0)
+ p = b;
+ else if (1 < i)
+ p = g1;
+ else if (0 < i)
+ p = g2;
+
+ sink (p);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ void *p = g3;
+ if (i < -1)
+ p = a;
+ else if (i < 0)
+ p = b;
+ else if (1 < i)
+ p = g1;
+ else if (0 < i)
+ p = g2;
+
+ sink (p);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_4_globals_1_local (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+
+ void *p = g3;
+ if (i < -1)
+ p = a;
+ else if (i < 0)
+ p = g1;
+ else if (1 < i)
+ p = g2;
+ else if (0 < i)
+ p = g4;
+
+ sink (p);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_all_globals (int i)
+{
+ void *p = g4;
+ if (i < -1)
+ p = g1;
+ else if (i < 0)
+ p = g2;
+ else if (1 < i)
+ p = g3;
+ else if (0 < i)
+ p = g5;
+ return p;
+}
+
+
+ATTR (noipa) void*
+return_2_alloca_local_cstoff (int n, int i)
+{
+ int *a = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int *b = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int *p = i < 0 ? a : b;
+
+ p += 1;
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_alloca_local_cstoff (int n, int i)
+{
+ int *a = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ int *p = b;
+ if (i < 0)
+ p = a;
+
+ p += 1;
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_local_alloca_cstoff (int n, int i)
+{
+ int a[2]; /* { dg-message "declared here" } */
+ int *b = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int *p = b;
+ if (i < 0)
+ p = a;
+
+ p += 1;
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_locals_cstoff (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ int *p = b;
+ if (i < 0)
+ p = a;
+
+ p += 1;
+ sink (p);
+
+ return p; /* { dg-warning "function returns address of local" } */
+}
+
+ATTR (noipa) void*
+return_2_globals_3_locals_cstoff (int i)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+ int c[3]; /* { dg-message "declared here" } */
+
+ int *p = c;
+ if (i < -1)
+ p = a;
+ else if (i < 0)
+ p = b;
+ else if (1 < i)
+ p = g1;
+ else if (0 < i)
+ p = g2;
+
+ p += 1;
+ sink (p);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_alloca_local_varoff (int n, int i, int j)
+{
+ int *a = __builtin_alloca (n); /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ int *p = g3;
+ if (i < -1)
+ p = a;
+ else if (i < 0)
+ p = b;
+ else if (1 < i)
+ p = g1;
+ else if (0 < i)
+ p = g2;
+
+ p += j;
+ sink (p);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
+ATTR (noipa) void*
+return_3_globals_2_locals_varoff (int i, int j)
+{
+ int a[1]; /* { dg-message "declared here" } */
+ int b[2]; /* { dg-message "declared here" } */
+
+ int *p = g3;
+ if (i < -1)
+ p = a;
+ else if (i < 0)
+ p = b;
+ else if (1 < i)
+ p = g1;
+ else if (0 < i)
+ p = g2;
+
+ p += j;
+ sink (p);
+
+ return p; /* { dg-warning "function may return address of local" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-5.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-5.c
new file mode 100644
index 00000000000..bdf1cd40c1b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-5.c
@@ -0,0 +1,40 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+ { dg-do compile }
+ { dg-options "-O2 -Wall" } */
+
+void sink (void*);
+
+void* loop_idx (int x)
+{
+ char a[32]; /* { dg-message "declared here" } */
+ char *p = a;
+
+ sink (a);
+
+ int i;
+ for (i = 0; i != 32; ++i)
+ if (p[i] == x)
+ break;
+
+ p = i < 32 ? &p[i] : 0;
+ return p; /* { dg-warning "may return address of local variable" } */
+}
+
+
+void* loop_ptr (int i, int x)
+{
+ char a[32]; /* { dg-message "declared here" } */
+ char *p;
+
+ sink (a);
+
+ /* The warning for the statement below would ideally be a "returns"
+ because it definitely returns the address of a, but when both
+ returns get merged into one we end up with a "may return". */
+ for (p = a; *p; ++p)
+ if (*p == x)
+ return p; /* { dg-warning "(returns|may return) address of local variable" "missing location" { xfail *-*-* } } */
+ /* { dg-warning "(returns|may return) address of local variable" "pr90735" { target *-*-* } 0 } */
+
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-6.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-6.c
new file mode 100644
index 00000000000..70138b3eff8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-6.c
@@ -0,0 +1,203 @@
+/* PR c/71924 - missing -Wreturn-local-addr returning alloca result
+ { dg-do compile }
+ { dg-options "-O2 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* memcpy (void*, const void*, size_t);
+void* mempcpy (void*, const void*, size_t);
+void* memmove (void*, const void*, size_t);
+
+char* stpcpy (char*, const char*);
+char* stpncpy (char*, const char*, size_t);
+
+size_t strlen (const char*);
+size_t strnlen (const char*, size_t);
+
+char* strcat (char*, const char*);
+char* strncat (char*, const char*, size_t);
+
+char* strcpy (char*, const char*);
+char* strncpy (char*, const char*, size_t);
+
+char* strdup (const char*);
+
+char* strchr (const char*, int);
+char* strrchr (const char*, int);
+char* strstr (const char*, const char*);
+
+void sink (void*, ...);
+
+
+void* return_memcpy (const void *s, unsigned n)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ void *p = memcpy (a, s, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memcpy_cst (const void *s, unsigned n)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ void *p = memcpy (a + 1, s, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memcpy_var (const void *s, unsigned n, int i)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ void *p = memcpy (a + i, s, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_mempcpy (const void *s, unsigned n)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ void *p = mempcpy (a, s, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memmove_cst (unsigned n)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ void *p = memmove (a + 1, a, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+void* return_memmove_var (unsigned n, int i)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ void *p = memmove (a + i, a, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_stpcpy (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ char *p = stpcpy (a, s);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_stpncpy (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ char *p = stpncpy (a, s, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcat (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ char *p = strcat (a, s);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strncat (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ char *p = strncat (a, s, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+
+}
+char* return_strcpy (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ char *p = strcpy (a, s);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcpy_plus_strlen (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ char *p = strcpy (a, s);
+ sink (p);
+ p += strlen (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcpy_cst_plus_strlen (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ char *p = strcpy (a + 1, s);
+ sink (p);
+ p += strlen (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strcpy_var_plus_strlen (unsigned n, const char *s, int i)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ char *p = strcpy (a + i, s);
+ sink (p);
+ p += strlen (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strncpy (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ char *p = strncpy (a, s, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strncpy_plus_strnlen (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ char *p = strncpy (a, s, n);
+ p += strnlen (p, n);
+ sink (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strdup (unsigned n)
+{
+ char a[n];
+ sink (a);
+ char *p = strdup (a);
+ return p;
+}
+
+char* return_strchr (unsigned n, int c)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ char *p = strchr (a, c);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strstr (unsigned n, const char *s)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ char *p = strstr (a, s);
+ if (p)
+ p += strlen (p);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+}
+
+char* return_strrchr (unsigned n, int c)
+{
+ char a[n]; /* { dg-message "declared here" } */
+ sink (a);
+ char *p = strrchr (a, c);
+ return p; /* { dg-warning "\\\[-Wreturn-local-addr]" } */
+
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-7.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-7.c
new file mode 100644
index 00000000000..ac1fb769ba8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-7.c
@@ -0,0 +1,50 @@
+/* Test to verify that a PHI with a COND_EXPR argument in a return
+ statement is handled correctly.
+ { dg-do compile }
+ { dg-options "-O2 -Wall" } */
+
+extern struct S s;
+
+void* f (int n)
+{
+ void *p;
+ int x = 0;
+
+ for (int i = n; i >= 0; i--)
+ {
+ p = &s;
+ if (p == (void*)-1)
+ x = 1;
+ else if (p)
+ return p;
+ }
+
+ /* The return statement below ends up with the following IL:
+ <bb 6> [local count: 59055800]:
+ # x_10 = PHI <1(5), 0(2)>
+ _5 = x_10 != 0 ? -1B : 0B;
+
+ <bb 7> [local count: 114863532]:
+ # _3 = PHI <&s(4), _5(6), &s(3)>
+ return _3; */
+ return x ? (void*)-1 : 0;
+}
+
+void* g (int n)
+{
+ void *p;
+ int x = 0; /* { dg-message "declared here" } */
+
+ for (int i = n; i >= 0; i--)
+ {
+ p = &s;
+ if (p == (void*)-1)
+ x = 1;
+ else if (p)
+ return p;
+ }
+
+ /* The return statement below does not reference a COND_EXPR argument. */
+ return x ? &x : 0; /* { dg-warning "may return address of local variable" "missing location" { xfail *-*-* } } */
+ /* { dg-warning "may return address of local variable" "pr90735" { target *-*-* } 0 } */
+}
diff --git a/gcc/testsuite/gcc.dg/Wreturn-local-addr-8.c b/gcc/testsuite/gcc.dg/Wreturn-local-addr-8.c
new file mode 100644
index 00000000000..94707c3d008
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wreturn-local-addr-8.c
@@ -0,0 +1,49 @@
+/* Test to verify that a MAX_EXPR and MIN_EXPR in a return statement
+ is handled correctly and that all local variables whose address
+ is or may be returned are identified.
+ { dg-do compile }
+ { dg-options "-O2 -Wall" } */
+
+char* sink (char*, ...);
+
+void* test_max_2 (void)
+{
+ char c; /* { dg-message "declared here" } */
+
+ char *p = sink (&c);
+
+ void *q = p > &c ? p : &c; /* MAX_EXPR */
+ return q; /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
+
+void* test_max_3 (void)
+{
+ char c; /* { dg-message "declared here" } */
+ char d; /* { dg-message "declared here" } */
+
+ char *p = sink (&c, &d);
+
+ void *q = p < &c ? &c < &d ? &d : &c : p;
+ return q; /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
+
+void* test_min_2 (void)
+{
+ char c; /* { dg-message "declared here" } */
+
+ char *p = sink (&c);
+
+ void *q = p < &c ? p : &c; /* MIN_EXPR" */
+ return q; /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
+
+void* test_min_3 (void)
+{
+ char c; /* { dg-message "declared here" } */
+ char d; /* { dg-message "declared here" } */
+
+ char *p = sink (&c, &d);
+
+ void *q = p > &c ? &c > &d ? &d : &c : p;
+ return q; /* { dg-warning "\\\[-Wreturn-local-addr" } */
+}
diff --git a/gcc/testsuite/gcc.dg/pr41551.c b/gcc/testsuite/gcc.dg/pr41551.c
index 2f2ad2be97e..e1123206cc6 100644
--- a/gcc/testsuite/gcc.dg/pr41551.c
+++ b/gcc/testsuite/gcc.dg/pr41551.c
@@ -10,3 +10,5 @@ int main(void)
int var, *p = &var;
return (double)(uintptr_t)(p);
}
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/pr59523.c b/gcc/testsuite/gcc.dg/pr59523.c
index a6c3302a683..49cbe5dd27a 100644
--- a/gcc/testsuite/gcc.dg/pr59523.c
+++ b/gcc/testsuite/gcc.dg/pr59523.c
@@ -16,3 +16,5 @@ foo (int a, int *b, int *c, int *d)
r[i] = 1;
return r;
}
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c b/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c
index 292ce6edefc..ed5df826432 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr88775-2.c
@@ -41,3 +41,5 @@ f5 (void)
int c[64] = {}, d[64] = {};
return (__UINTPTR_TYPE__) &c[64] != (__UINTPTR_TYPE__) &d[0];
}
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/gcc/testsuite/gcc.dg/winline-7.c b/gcc/testsuite/gcc.dg/winline-7.c
index 34deca42592..239d748926d 100644
--- a/gcc/testsuite/gcc.dg/winline-7.c
+++ b/gcc/testsuite/gcc.dg/winline-7.c
@@ -13,3 +13,5 @@ inline void *t (void)
{
return q (); /* { dg-message "called from here" } */
}
+
+/* { dg-prune-output "-Wreturn-local-addr" } */
diff --git a/libgcc/generic-morestack.c b/libgcc/generic-morestack.c
index 0f6f0005f99..2dc373305fb 100644
--- a/libgcc/generic-morestack.c
+++ b/libgcc/generic-morestack.c
@@ -23,6 +23,8 @@ a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
+#pragma GCC optimize ("no-isolate-erroneous-paths-dereference")
+
/* powerpc 32-bit not supported. */
#if !defined __powerpc__ || defined __powerpc64__