Repository: mesonbuild/meson

Issue:
cc.has_function() requires correct includes
In autoconf this just works (for example):

```
AC_CHECK_FUNCS(setenv unsetenv)
```

whereas in meson this doesn't work:

```
cc.has_function ('setenv')
```

but this does:

```
cc.has_function ('setenv', prefix : '#include <stdlib.h>')
```

which is a bit unfortunate, because (a) it's more tedious especially if there are multiple includes and things like _GNU_SOURCE need to be defined, and (b) the required includes are not necessarily the same on all platforms.



Discussion:
In addition, there may also be something wrong (or at least different) with how autotools and meson check for a function.  `cc.has_function('lchmod', prefix : '#include <sys/stat.h>\n#include <unistd.h>')` works in meson but `AC_CHECK_FUNC(lchmod)` fails because the test program fails to compile because of

```
#if defined __stub_lchmod || defined __stub___lchmod
choke me
#endif
```

Maybe this warrants a different bug, but I think the fix may be the same for both.

Why is the `__stub_lchmod` there and is this specific to lchmod or are there corresponding defines for other functions as well?

__stub_.\* symbols are defined in &lt;gnu/stubs.h&gt; and pulled in by &lt;features.h&gt; . The purpose is to have stubs for syscalls that aren't supported by the underlying kernel (but glibc needs to export those symbols anyway). They're supposed to just return failure and set errno to ENOSYS.

So, to actually test if a glibc syscall is available on the system, you need to check that __stub functions aren't defined. Contents of stubs-64.h for me:

```
/* This file is automatically generated.
   It defines a symbol `__stub_FUNCTION' for each function
   in the C library which is a stub, meaning it will fail
   every time called, usually setting errno to ENOSYS.  */

#ifdef _LIBC
 #error Applications may not define the macro _LIBC
#endif

#define __stub_bdflush
#define __stub_chflags
#define __stub_fattach
#define __stub_fchflags
#define __stub_fdetach
#define __stub_getmsg
#define __stub_gtty
#define __stub_lchmod
#define __stub_putmsg
#define __stub_revoke
#define __stub_setlogin
#define __stub_sigreturn
#define __stub_sstk
#define __stub_stty
```


Resolving patch:
diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py
--- a/mesonbuild/compilers.py
+++ b/mesonbuild/compilers.py
@@ -258,6 +258,9 @@ def get_option_link_args(self, options):
     def has_header(self, *args, **kwargs):
         raise EnvironmentException('Language %s does not support header checks.' % self.language)
 
+    def has_header_symbol(self, *args, **kwargs):
+        raise EnvironmentException('Language %s does not support header symbol checks.' % self.language)
+
     def compiles(self, *args, **kwargs):
         raise EnvironmentException('Language %s does not support compile checks.' % self.language)
 
@@ -347,6 +350,9 @@ def get_linker_exelist(self):
     def get_compile_only_args(self):
         return ['-c']
 
+    def get_no_optimization_args(self):
+        return ['-O0']
+
     def get_output_args(self, target):
         return ['-o', target]
 
@@ -460,6 +466,14 @@ def has_header(self, hname, extra_args=[]):
 '''
         return self.compiles(templ % hname, extra_args)
 
+    def has_header_symbol(self, hname, symbol, prefix, extra_args=[]):
+        templ = '''{2}
+#include <{0}>
+int main () {{ {1}; }}'''
+        # Pass -O0 to ensure that the symbol isn't optimized away
+        extra_args += self.get_no_optimization_args()
+        return self.compiles(templ.format(hname, symbol, prefix), extra_args)
+
     def compile(self, code, srcname, extra_args=[]):
         commands = self.get_exelist()
         commands.append(srcname)
@@ -637,15 +651,41 @@ def alignment(self, typename, env, extra_args=[]):
         return align
 
     def has_function(self, funcname, prefix, env, extra_args=[]):
-        # This fails (returns true) if funcname is a ptr or a variable.
-        # The correct check is a lot more difficult.
-        # Fix this to do that eventually.
-        templ = '''%s
-int main(int argc, char **argv) {
-    void *ptr = (void*)(%s);
-    return 0;
-};
-'''
+        # Define the symbol to something else in case it is defined by the
+        # includes or defines listed by the user `{0}` or by the compiler.
+        # Then, undef the symbol to get rid of it completely.
+        templ = '''
+        #define {1} meson_disable_define_of_{1}
+        {0}
+        #undef {1}
+        '''
+
+        # Override any GCC internal prototype and declare our own definition for
+        # the symbol. Use char because that's unlikely to be an actual return
+        # value for a function which ensures that we override the definition.
+        templ += '''
+        #ifdef __cplusplus
+        extern "C"
+        #endif
+        char {1} ();
+        '''
+
+        # glibc defines functions that are not available on Linux as stubs that
+        # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail
+        # instead of detecting the stub as a valid symbol.
+        templ += '''
+        #if defined __stub_{1} || defined __stub___{1}
+        fail fail fail this function is not going to work
+        #endif
+        '''
+
+        # And finally the actual function call
+        templ += '''
+        int
+        main ()
+        {{
+          return {1} ();
+        }}'''
         varname = 'has function ' + funcname
         varname = varname.replace(' ', '_')
         if self.is_cross:
@@ -654,7 +694,15 @@ def has_function(self, funcname, prefix, env, extra_args=[]):
                 if isinstance(val, bool):
                     return val
                 raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname))
-        return self.compiles(templ % (prefix, funcname), extra_args)
+        if self.links(templ.format(prefix, funcname), extra_args):
+            return True
+        # Some functions like alloca() are defined as compiler built-ins which
+        # are inlined by the compiler, so test for that instead. Built-ins are
+        # special functions that ignore all includes and defines, so we just
+        # directly try to link via main().
+        # Add -O0 to ensure that the symbol isn't optimized away by the compiler
+        extra_args += self.get_no_optimization_args()
+        return self.links('int main() {{ {0}; }}'.format('__builtin_' + funcname), extra_args)
 
     def has_member(self, typename, membername, prefix, extra_args=[]):
         templ = '''%s
@@ -1255,6 +1303,9 @@ def get_pch_use_args(self, pch_dir, header):
     def get_compile_only_args(self):
         return ['/c']
 
+    def get_no_optimization_args(self):
+        return ['/Od']
+
     def get_output_args(self, target):
         if target.endswith('.exe'):
             return ['/Fe' + target]
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -575,6 +575,7 @@ def __init__(self, compiler, env):
                              'get_id': self.get_id_method,
                              'sizeof': self.sizeof_method,
                              'has_header': self.has_header_method,
+                             'has_header_symbol': self.has_header_symbol_method,
                              'run' : self.run_method,
                              'has_function' : self.has_function_method,
                              'has_member' : self.has_member_method,
@@ -752,6 +753,24 @@ def has_header_method(self, args, kwargs):
         mlog.log('Has header "%s":' % string, h)
         return haz
 
+    def has_header_symbol_method(self, args, kwargs):
+        if len(args) != 2:
+            raise InterpreterException('has_header_symbol method takes exactly two arguments.')
+        check_stringlist(args)
+        hname = args[0]
+        symbol = args[1]
+        prefix = kwargs.get('prefix', '')
+        if not isinstance(prefix, str):
+            raise InterpreterException('Prefix argument of has_function must be a string.')
+        extra_args = self.determine_args(kwargs)
+        haz = self.compiler.has_header_symbol(hname, symbol, prefix, extra_args)
+        if haz:
+            h = mlog.green('YES')
+        else:
+            h = mlog.red('NO')
+        mlog.log('Header <{0}> has symbol "{1}":'.format(hname, symbol), h)
+        return haz
+
     def find_library_method(self, args, kwargs):
         if len(args) != 1:
             raise InterpreterException('find_library method takes one argument.')


Test patch:
diff --git a/test cases/common/111 has header symbol/meson.build b/test cases/common/111 has header symbol/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/111 has header symbol/meson.build	
@@ -0,0 +1,18 @@
+project('has header symbol', 'c')
+
+cc = meson.get_compiler('c')
+
+assert (cc.has_header_symbol('stdio.h', 'int'), 'base types should always be available')
+assert (cc.has_header_symbol('stdio.h', 'printf'), 'printf function not found')
+assert (cc.has_header_symbol('stdio.h', 'FILE'), 'FILE structure not found')
+assert (cc.has_header_symbol('limits.h', 'INT_MAX'), 'INT_MAX define not found')
+assert (not cc.has_header_symbol('limits.h', 'guint64'), 'guint64 is not defined in limits.h')
+assert (not cc.has_header_symbol('stdlib.h', 'FILE'), 'FILE structure is defined in stdio.h, not stdlib.h')
+assert (not cc.has_header_symbol('stdlol.h', 'printf'), 'stdlol.h shouldn\'t exist')
+assert (not cc.has_header_symbol('stdlol.h', 'int'), 'shouldn\'t be able to find "int" with invalid header')
+
+# This is likely only available on Glibc, so just test for it
+if cc.has_function('ppoll')
+  assert (not cc.has_header_symbol('poll.h', 'ppoll'), 'ppoll should not be accessible without _GNU_SOURCE')
+  assert (cc.has_header_symbol('poll.h', 'ppoll', prefix : '#define _GNU_SOURCE'), 'ppoll should be accessible with _GNU_SOURCE')
+endif
diff --git a/test cases/common/43 has function/meson.build b/test cases/common/43 has function/meson.build
--- a/test cases/common/43 has function/meson.build	
+++ b/test cases/common/43 has function/meson.build	
@@ -6,6 +6,30 @@ if not cc.has_function('printf', prefix : '#include<stdio.h>')
   error('Existing function not found.')
 endif
 
+# Should also be able to detect it without specifying the header
+# We check for a different function here to make sure the result is
+# not taken from a cache (ie. the check above)
+assert(cc.has_function('fprintf'), 'Existing function not found without include')
+
 if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>')
   error('Found non-existant function.')
 endif
+
+# With glibc on Linux lchmod is a stub that will always return an error,
+# we want to detect that and declare that the function is not available.
+# We can't check for the C library used here of course, but if it's not
+# implemented in glibc it's probably not implemented in any other 'slimmer'
+# C library variants either, so the check should be safe either way hopefully.
+if host_machine.system() == 'linux' and cc.get_id() == 'gcc'
+  assert (cc.has_function('poll', prefix : '#include <poll.h>'), 'couldn\'t detect poll when defined by a header')
+  assert (not cc.has_function('lchmod', prefix : '''#include <sys/stat.h>
+                                                    #include <unistd.h>'''), 'lchmod check should have failed')
+endif
+
+# For some functions one needs to define _GNU_SOURCE before including the
+# right headers to get them picked up. Make sure we can detect these functions
+# as well without any prefix
+if cc.has_header_symbol('sys/socket.h', 'recvmmsg', prefix : '#define _GNU_SOURCE')
+  # We assume that if recvmmsg exists sendmmsg does too
+  assert (cc.has_function('sendmmsg'), 'Failed to detect existing function')
+endif