Sarcasm / irony-mode

A C/C++ minor mode for Emacs powered by libclang
GNU General Public License v3.0
904 stars 99 forks source link

Issue with -stdlib=libc++ #433

Closed ddovod closed 6 years ago

ddovod commented 6 years ago

Hello. First, thank you a lot for such useful plugin!

I have some issue with projects which use libc++. My setup: Xubuntu 17.04 Emacs 25.3.2 clang 5.0 (from llvm ppa) libc++ and libc++abi 5.0 (built from source due to lack of prebuilt packages, install prefix = /usr, so the headers are located in /usr/include/c++/v1) latest flyckeck, irony-mode, company-irony, flycheck-irony packages with "default" configurations. Actually I also use cpputils-cmake, but I think it doesn't matter in this case.

Test project is very straightforward: main.cpp

#include <functional>

int main()
{
    std::function<void(int)> aaa;
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.2)
project(cpp_test)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(SOURCES main.cpp)

add_executable(cpp_test ${SOURCES})
target_compile_options(cpp_test PRIVATE -std=c++11)

This setup produces compile_commands.json

[
{
  "directory": "/home/ddovod/_private/trash/cpp_test/build",
  "command": "/usr/bin/c++       -std=c++11 -o CMakeFiles/cpp_test.dir/main.cpp.o -c /home/ddovod/_private/trash/cpp_test/main.cpp",
  "file": "/home/ddovod/_private/trash/cpp_test/main.cpp"
}
]

By default libstdc++ is used, and in this case all works fine, I can get working code completion and flycheck. But if I add -stdlib=libc++ inside target_compile_options, completion and syntax check of std stuff becomes broken screenshot_2017-09-18_00-42-03

BTW in both cases program compiles successful. So I'm suspecting some internal logic of irony or maybe libclang. Could you help me please with it? I can provide any additional info if you need one. Thank you again!

ddovod commented 6 years ago

Today I have tried to parse this file with simple python script

import clang.cindex

idx = clang.cindex.Index.create()
tu = idx.parse('main.cpp', args=['-std=c++11', '-stdlib=libc++'])
for el in tu.diagnostics:
    print(el.spelling)

In both cases, with and without -stdlib=libc++, there're no errors (clear output), so maybe you can look in irony code, or maybe you have some idea about such behavior? Btw, it can be helpful

ddovod@/cpp_test: ~/.emacs.d/irony/bin/irony-server --version
irony-server version 1.1.0
clang version 5.0.0-svn312333-1~exp1 (branches/release_50)

Thank you

ddovod commented 6 years ago

And also it could be helpful, interactive irony-server session

ddovod@/cpp_test: ~/.emacs.d/irony/bin/irony-server -i
parse main.cpp -- -std=c++11 -stdlib=libc++
execute: Command{action=Command::Parse, file='main.cpp', unsavedFile='', dir='', line=0, column=0, prefix='', caseStyle='exact', flags=['-std=c++11', '-stdlib=libc++'], opt=off}
(success . t)

;;EOT
diagnostics
execute: Command{action=Command::Diagnostics, file='', unsavedFile='', dir='', line=0, column=0, prefix='', caseStyle='exact', flags=[], opt=off}
(
("/usr/include/c++/v1/cstddef" 44 15 810 fatal "'stddef.h' file not found")
("/usr/include/c++/v1/cstddef" 49 9 880 error "no member named 'ptrdiff_t' in the global namespace")
("/usr/include/c++/v1/cstddef" 50 9 899 error "no member named 'size_t' in the global namespace")
("/usr/include/c++/v1/type_traits" 539 44 24935 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 542 32 24994 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 559 42 25492 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 562 32 25550 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 578 30 25980 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 591 29 26387 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 610 79 26958 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 611 79 27051 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 620 85 27329 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 621 85 27428 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 654 61 28597 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 655 61 28672 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 667 64 29053 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 668 69 29136 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 685 79 29721 error "base specifier must name a class")
("/usr/include/c++/v1/type_traits" 686 79 29814 error "base specifier must name a class")
("/home/ddovod/_private/trash/cpp_test/main.cpp" 6 30 66 error "implicit instantiation of undefined template 'std::__1::function<void (int)>'")
)

;;EOT

But

ddovod@/cpp_test: ls -l /usr/include/c++/v1/ | grep stddef
-rw-r--r--  1 root root   2409 сен 17 18:11 cstddef
-rw-r--r--  1 root root   1297 сен 17 18:11 stddef.h
ddovod commented 6 years ago

Okay, it seems I finally managed to make it work. After commenting lines 100-101 in TUManager.cpp

     argv.push_back("-isystem");
     argv.push_back(CLANG_BUILTIN_HEADERS_DIR);

it works as expected with -stdlib=libc++, also with same interactive session there're no errors about missing stddef.h. It's ugly hack, so I hope you have better solution for this

Sarcasm commented 6 years ago

Oh interesting, I'm not sure how to handle this situation. A few questions:

ddovod commented 6 years ago
  1. Yes, at least there're no errors from flycheck and irony-server -i session
  2. Yes, it is in the root of headers of libc++
    ddovod@/build: ls -l /usr/include/c++/v1/ | grep stddef
    -rw-r--r--  1 root root   2409 сен 17 18:11 cstddef
    -rw-r--r--  1 root root   1297 сен 17 18:11 stddef.h

    Another stddef.h is in the clang includes

    ddovod@/cpp_test: ls -l /usr/lib/llvm-5.0/lib/clang/5.0.0/include | grep stddef
    -rw-r--r-- 1 root root    4498 сен  1 20:30 stddef.h
    -rw-r--r-- 1 root root    1770 сен  1 20:30 __stddef_max_align_t.h
  3. ddovod@/build: cmake -L .. | grep CHECK_LIBCLANG_BUILTIN_HEADERS
    CHECK_LIBCLANG_BUILTIN_HEADERS_STDDEF_DIR:PATH=/usr/lib/llvm-5.0/lib/clang/5.0.0/include

    Also these 2 stddef.hs are totally different, I think they do about one work, but their content is absolutely different

Sarcasm commented 6 years ago

And do you know where is your GCC/libstdc++ stddef.h?

ddovod commented 6 years ago

For gcc it is in /usr/lib/gcc/x86_64-linux-gnu/6/include/stddef.h For libstdc++ - I don't know actually, find onlu found this, libc++ and linux stddefs. But the content of cstddef in libstdc++ is

#ifndef _GLIBCXX_CSTDDEF
#define _GLIBCXX_CSTDDEF 1

#pragma GCC system_header

#undef __need_wchar_t
#undef __need_ptrdiff_t
#undef __need_size_t
#undef __need_NULL
#undef __need_wint_t
#include <bits/c++config.h>
#include <stddef.h>

#if __cplusplus >= 201103L
namespace std
{
  // We handle size_t, ptrdiff_t, and nullptr_t in c++config.h.
  using ::max_align_t;
}
#endif

#endif // _GLIBCXX_CSTDDEF

and in libc++

#ifndef _LIBCPP_CSTDDEF
#define _LIBCPP_CSTDDEF

/*
    cstddef synopsis

Macros:

    offsetof(type,member-designator)
    NULL

namespace std
{

Types:

    ptrdiff_t
    size_t
    max_align_t
    nullptr_t
    byte // C++17

}  // std

*/

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

// Don't include our own <stddef.h>; we don't want to declare ::nullptr_t.
#include_next <stddef.h>
#include <__nullptr>

_LIBCPP_BEGIN_NAMESPACE_STD

using ::ptrdiff_t;
using ::size_t;

#if defined(__CLANG_MAX_ALIGN_T_DEFINED) || defined(_GCC_MAX_ALIGN_T) || \
    defined(__DEFINED_max_align_t)
// Re-use the compiler's <stddef.h> max_align_t where possible.
using ::max_align_t;
#else
typedef long double max_align_t;
#endif

_LIBCPP_END_NAMESPACE_STD

#if _LIBCPP_STD_VER > 14
namespace std  // purposefully not versioned
{
enum class byte : unsigned char {};

constexpr byte& operator|=(byte& __lhs, byte __rhs) noexcept
{ return __lhs = byte(static_cast<unsigned char>(__lhs) | static_cast<unsigned char>(__rhs)); }
constexpr byte  operator| (byte  __lhs, byte __rhs) noexcept
{ return         byte(static_cast<unsigned char>(__lhs) | static_cast<unsigned char>(__rhs)); }

constexpr byte& operator&=(byte& __lhs, byte __rhs) noexcept
{ return __lhs = byte(static_cast<unsigned char>(__lhs) & static_cast<unsigned char>(__rhs)); }
constexpr byte  operator& (byte  __lhs, byte __rhs) noexcept
{ return         byte(static_cast<unsigned char>(__lhs) & static_cast<unsigned char>(__rhs)); }

constexpr byte& operator^=(byte& __lhs, byte __rhs) noexcept 
{ return __lhs = byte(static_cast<unsigned char>(__lhs) ^ static_cast<unsigned char>(__rhs)); }
constexpr byte  operator^ (byte  __lhs, byte __rhs) noexcept
{ return         byte(static_cast<unsigned char>(__lhs) ^ static_cast<unsigned char>(__rhs)); }

constexpr byte  operator~ (byte __b) noexcept
{ return  byte(~static_cast<unsigned char>(__b)); }

}

#include <type_traits>  // rest of byte
#endif

#endif  // _LIBCPP_CSTDDEF

Maybe it can help somehow

Sarcasm commented 6 years ago

I'm actually more curious about stddef.h: What is the content of these two?

  1. /usr/include/c++/v1/stddef.h
  2. /usr/lib/llvm-5.0/lib/clang/5.0.0/include/stddef.h
ddovod commented 6 years ago

1.

// -*- C++ -*-
//===--------------------------- stddef.h ---------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#if defined(__need_ptrdiff_t) || defined(__need_size_t) || \
    defined(__need_wchar_t) || defined(__need_NULL) || defined(__need_wint_t)

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#include_next <stddef.h>

#elif !defined(_LIBCPP_STDDEF_H)
#define _LIBCPP_STDDEF_H

/*
    stddef.h synopsis

Macros:

    offsetof(type,member-designator)
    NULL

Types:

    ptrdiff_t
    size_t
    max_align_t
    nullptr_t

*/

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#include_next <stddef.h>

#ifdef __cplusplus

extern "C++" {
#include <__nullptr>
using std::nullptr_t;
}

// Re-use the compiler's <stddef.h> max_align_t where possible.
#if !defined(__CLANG_MAX_ALIGN_T_DEFINED) && !defined(_GCC_MAX_ALIGN_T) && \
    !defined(__DEFINED_max_align_t)
typedef long double max_align_t;
#endif

#endif

#endif  // _LIBCPP_STDDEF_H

2.

/*===---- stddef.h - Basic type definitions --------------------------------===
 *
 * Copyright (c) 2008 Eli Friedman
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 *===-----------------------------------------------------------------------===
 */

#if !defined(__STDDEF_H) || defined(__need_ptrdiff_t) ||                       \
    defined(__need_size_t) || defined(__need_wchar_t) ||                       \
    defined(__need_NULL) || defined(__need_wint_t)

#if !defined(__need_ptrdiff_t) && !defined(__need_size_t) &&                   \
    !defined(__need_wchar_t) && !defined(__need_NULL) &&                       \
    !defined(__need_wint_t)
/* Always define miscellaneous pieces when modules are available. */
#if !__has_feature(modules)
#define __STDDEF_H
#endif
#define __need_ptrdiff_t
#define __need_size_t
#define __need_wchar_t
#define __need_NULL
#define __need_STDDEF_H_misc
/* __need_wint_t is intentionally not defined here. */
#endif

#if defined(__need_ptrdiff_t)
#if !defined(_PTRDIFF_T) || __has_feature(modules)
/* Always define ptrdiff_t when modules are available. */
#if !__has_feature(modules)
#define _PTRDIFF_T
#endif
typedef __PTRDIFF_TYPE__ ptrdiff_t;
#endif
#undef __need_ptrdiff_t
#endif /* defined(__need_ptrdiff_t) */

#if defined(__need_size_t)
#if !defined(_SIZE_T) || __has_feature(modules)
/* Always define size_t when modules are available. */
#if !__has_feature(modules)
#define _SIZE_T
#endif
typedef __SIZE_TYPE__ size_t;
#endif
#undef __need_size_t
#endif /*defined(__need_size_t) */

#if defined(__need_STDDEF_H_misc)
/* ISO9899:2011 7.20 (C11 Annex K): Define rsize_t if __STDC_WANT_LIB_EXT1__ is
 * enabled. */
#if (defined(__STDC_WANT_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__ >= 1 && \
     !defined(_RSIZE_T)) || __has_feature(modules)
/* Always define rsize_t when modules are available. */
#if !__has_feature(modules)
#define _RSIZE_T
#endif
typedef __SIZE_TYPE__ rsize_t;
#endif
#endif /* defined(__need_STDDEF_H_misc) */

#if defined(__need_wchar_t)
#ifndef __cplusplus
/* Always define wchar_t when modules are available. */
#if !defined(_WCHAR_T) || __has_feature(modules)
#if !__has_feature(modules)
#define _WCHAR_T
#if defined(_MSC_EXTENSIONS)
#define _WCHAR_T_DEFINED
#endif
#endif
typedef __WCHAR_TYPE__ wchar_t;
#endif
#endif
#undef __need_wchar_t
#endif /* defined(__need_wchar_t) */

#if defined(__need_NULL)
#undef NULL
#ifdef __cplusplus
#  if !defined(__MINGW32__) && !defined(_MSC_VER)
#    define NULL __null
#  else
#    define NULL 0
#  endif
#else
#  define NULL ((void*)0)
#endif
#ifdef __cplusplus
#if defined(_MSC_EXTENSIONS) && defined(_NATIVE_NULLPTR_SUPPORTED)
namespace std { typedef decltype(nullptr) nullptr_t; }
using ::std::nullptr_t;
#endif
#endif
#undef __need_NULL
#endif /* defined(__need_NULL) */

#if defined(__need_STDDEF_H_misc)
#if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L
#include "__stddef_max_align_t.h"
#endif
#define offsetof(t, d) __builtin_offsetof(t, d)
#undef __need_STDDEF_H_misc
#endif  /* defined(__need_STDDEF_H_misc) */

/* Some C libraries expect to see a wint_t here. Others (notably MinGW) will use
__WINT_TYPE__ directly; accommodate both by requiring __need_wint_t */
#if defined(__need_wint_t)
/* Always define wint_t when modules are available. */
#if !defined(_WINT_T) || __has_feature(modules)
#if !__has_feature(modules)
#define _WINT_T
#endif
typedef __WINT_TYPE__ wint_t;
#endif
#undef __need_wint_t
#endif /* __need_wint_t */

#endif
DirtYiCE commented 6 years ago

@Sarcasm I was just about to open an issue about -isystem, but I'll just comment it here. Instead of using -isystem /path/to/clang/include, you should use -resource-dir /path/to/clang in irony. Using -isystem fucks up the include order, so the #include_next in libc++ won't find the correct stddef.h (as you can see in ("/usr/include/c++/v1/cstddef" 44 15 810 fatal "'stddef.h' file not found")).

See what happens if I manually specify -isystem on a standalone clang:

$ touch x.cpp
$ clang -v -stdlib=libc++ -E x.cpp -o /dev/null
[...snip...]
#include <...> search starts here:
 /usr/include/c++/v1
 /usr/local/include
 /usr/lib64/llvm/5/bin/../../../../lib/clang/5.0.0/include
 /usr/include
End of search list.
$ clang -v -stdlib=libc++ -E x.cpp -o /dev/null -isystem /usr/lib64/clang/5.0.0/include/
[...]
#include <...> search starts here:
 /usr/lib64/clang/5.0.0/include
 /usr/include/c++/v1
 /usr/local/include
 /usr/include
End of search list.
Sarcasm commented 6 years ago

Instead of using -isystem /path/to/clang/include, you should use -resource-dir /path/to/clang in irony

Wow! I thought I did, but you are right it is -isystem, and I cannot find a reason why. Thank you for finding this, it's not necessarily obvious... :+1:

Changed to -resource-dir, this change is slightly risky, we will see how it goes, it worked for me but I'm not sure that will be the case for all distributions. Let me know if it works for you guys.

Committed as 6c8b567abad2da4ce975e19f393d0cad05da2d99.

ddovod commented 6 years ago

@DirtYiCE Good catch! Just checked on my setup and it works fine without any manual patching. @Sarcasm I think we can close this issue now