Bug 80959 - -Wreturn-type "control reaches end of non-void function" false positive with -fsanitize=address
Summary: -Wreturn-type "control reaches end of non-void function" false positive with ...
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c (show other bugs)
Version: 7.1.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic, missed-optimization
Depends on:
Blocks:
 
Reported: 2017-06-02 12:46 UTC by Simon Marchi
Modified: 2022-11-17 02:50 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2017-06-02 00:00:00


Attachments
Reproducer (132 bytes, text/plain)
2017-06-02 12:46 UTC, Simon Marchi
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Simon Marchi 2017-06-02 12:46:41 UTC
Created attachment 41458 [details]
Reproducer

I stumbled on this while building the binutils-gdb repo with -fsanitize=address with gcc 7.1.0 and reproduced it with gcc master from today.  It doesn't happen with my distro compiler (5.4.0-6ubuntu1~16.04.4).

gcc reports that the control can reach the end of the function, but I think it's not the case.  This is the smallest reproducer I managed to find.  Note that the problem disappears if you enable optimizations (-O > 0) or remove -fsanitize=address.

Compile the attached file with:

$ /opt/gcc/git/bin/gcc -Wreturn-type -Werror -O0 -fsanitize=address -c test.c
test.c: In function 'foo':
test.c:23:1: error: control reaches end of non-void function [-Werror=return-type]
 }
 ^
cc1: all warnings being treated as errors
Comment 1 Simon Marchi 2017-06-02 12:49:17 UTC
Forgot to mention, the initial problem I stumbled on was this function:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=bfd/mach-o-i386.c;h=b2f02415c3ae019224b25184539237d530c5bc7e;hb=HEAD#l114
Comment 2 Richard Biener 2017-06-02 13:07:50 UTC
Looks like it is caused by

  try
    {
...
    }
  finally
    {
      ASAN_MARK (POISON, &n, 4);
    }

which eventually gets to

  <bb 5> [0.00%]:
  ASAN_MARK (POISON, &n, 4);
  switch (finally_tmp.2) <default: <L8> [0.00%], case 1: <L5> [0.00%]>

<L5> [0.00%]:

  <bb 7> [0.00%]:
  return;

<L8> [0.00%]:
  return D.2131;

thus it lacks a return.  That's also visible in .original:

{
  int n;

    int n;
  bar (&n);
  switch (i)
    {
      case 1:;
      switch (i)
        {
          default:;
          return 0;
        }
      goto <D.2128>;
      default:;
      return 0;
    }
  <D.2128>:;
}

but I guess that "dead" break; after the inner switch was previously
CFG cleaned-up.
Comment 3 Martin Liška 2017-06-02 14:04:37 UTC
Hm, is the GIMPLE instrumentation w/ ASAN_MARK properly done or is it issues in tree-cfg pass? I'm bit confused here.
Comment 4 Martin Liška 2017-06-08 10:33:41 UTC
So there's explanation what happens:

1) w/o -fsanitize=address:

decide_copy_try_finally returns true and so that we copy BB that contains finally statement:

foo ()
{
  int n;
  int D.1806;

  bar (&n);
  i.0_1 = i;
  switch (i.0_1) <default: <D.1804>, case 1: <D.1801>>
  <D.1801>:
  i.1_2 = i;
  switch (i.1_2) <default: <D.1802>>
  <D.1802>:
  D.1806 = 0;
  goto <D.1809>;
  goto <D.1803>;
  <D.1804>:
  D.1806 = 0;
  goto <D.1809>;
  <D.1803>:
  n = {CLOBBER};
  goto <D.1808>;
  <D.1809>:
  n = {CLOBBER};
  goto <D.1807>;
  <D.1808>:
  return;
  <D.1807>:
  return D.1806;
}

then CFG pass removes the dead BBs. All works fine.

2) w/ -fsanitize=address the decide_copy_try_finally returns false and thus we create switch to dispatch
from the finally statement:

foo ()
{
  int finally_tmp.2;
  int n;
  int D.2128;

  ASAN_MARK (UNPOISON, &n, 4);
  bar (&n);
  i.0_1 = i;
  switch (i.0_1) <default: <D.2126>, case 1: <D.2123>>
  <D.2123>:
  i.1_2 = i;
  switch (i.1_2) <default: <D.2124>>
  <D.2124>:
  D.2128 = 0;
  finally_tmp.2 = 0;
  goto <D.2131>;
  goto <D.2125>;
  <D.2126>:
  D.2128 = 0;
  finally_tmp.2 = 0;
  goto <D.2131>;
  <D.2125>:
  finally_tmp.2 = 1;
  <D.2131>:
  ASAN_MARK (POISON, &n, 4);
  switch (finally_tmp.2) <default: <D.2134>, case 1: <D.2132>>
  <D.2132>:
  goto <D.2133>;
  <D.2134>:
  goto <D.2129>;
  <D.2133>:
  return;
  <D.2129>:
  return D.2128;
}

Then CFG removes:

Removing basic block 7
;; basic block 7, loop depth 0
;;  pred:       5
finally_tmp.2 = 1;
;;  succ:       8

now   finally_tmp.2 can have only one value, but the switch statement,
as well as the problematic return BB are not removed.
Comment 5 Martin Liška 2017-06-08 10:40:46 UTC
Can be also simulated with ObjC:

$ cat /tmp/objc.m
volatile int i;

int
foo (void)
{
  @try {
    switch (i)
      {
      case 1:
	switch (i)
	  {
	  default:
	    return 0;
	  }
	break;
      default:
	return 0;
      }
  }
  @finally {
    i = 2;
  }
}

$ gcc /tmp/objc.m -fobjc-exceptions -c -Wall 
/tmp/objc.m: In function ‘foo’:
/tmp/objc.m:23:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

But as the function decide_copy_try_finally depends on optimization level:

$ gcc /tmp/objc.m -fobjc-exceptions -c -Wall -O1
[nothing]