Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++] Simplify the implementation of <stddef.h> #86843

Merged
merged 1 commit into from
Apr 2, 2024

Conversation

ldionne
Copy link
Member

@ldionne ldionne commented Mar 27, 2024

Libc++'s own <stddef.h> is complicated by the need to handle various platform-specific macros and to support duplicate inclusion. In reality, we only need to add a declaration of nullptr_t to it, so we can simply include the underlying <stddef.h> outside of our guards to let it handle re-inclusion itself.

Libc++'s own <stddef.h> is complicated by the need to handle various
platform-specific macros and to support duplicate inclusion. In reality,
we only need to add a declaration of nullptr_t to it, so we can simply
include the underlying <stddef.h> outside of our guards to let it handle
re-inclusion itself.
@ldionne ldionne requested a review from a team as a code owner March 27, 2024 18:00
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Mar 27, 2024
@llvmbot
Copy link
Member

llvmbot commented Mar 27, 2024

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

Changes

Libc++'s own <stddef.h> is complicated by the need to handle various platform-specific macros and to support duplicate inclusion. In reality, we only need to add a declaration of nullptr_t to it, so we can simply include the underlying <stddef.h> outside of our guards to let it handle re-inclusion itself.


Full diff: https://github.com/llvm/llvm-project/pull/86843.diff

1 Files Affected:

  • (modified) libcxx/include/stddef.h (+8-17)
diff --git a/libcxx/include/stddef.h b/libcxx/include/stddef.h
index 887776b150e49d..470b5408336c6d 100644
--- a/libcxx/include/stddef.h
+++ b/libcxx/include/stddef.h
@@ -7,18 +7,6 @@
 //
 //===----------------------------------------------------------------------===//
 
-#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
 
@@ -36,16 +24,19 @@
 
 */
 
-#  include <__config>
+#include <__config>
+
+// Note: This include is outside of header guards because we sometimes get included multiple times
+//       with different defines and the underlying <stddef.h> will know how to deal with that.
+#include_next <stddef.h>
+
+#ifndef _LIBCPP_STDDEF_H
+#  define _LIBCPP_STDDEF_H
 
 #  if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #    pragma GCC system_header
 #  endif
 
-#  if __has_include_next(<stddef.h>)
-#    include_next <stddef.h>
-#  endif
-
 #  ifdef __cplusplus
 typedef decltype(nullptr) nullptr_t;
 #  endif

@ldionne
Copy link
Member Author

ldionne commented Mar 28, 2024

I am about to go OOO for a few days and I want to be around when I land this patch in case there is some unexpected fallout. So I'll merge this around Tuesday April 2nd.

@ldionne ldionne merged commit 2950283 into llvm:main Apr 2, 2024
53 checks passed
@ldionne ldionne deleted the review/stddef-rewrite branch April 2, 2024 12:14
llvmbot pushed a commit to llvmbot/llvm-project that referenced this pull request Apr 2, 2024
Libc++'s own <stddef.h> is complicated by the need to handle various
platform-specific macros and to support duplicate inclusion. In reality,
we only need to add a declaration of nullptr_t to it, so we can simply
include the underlying <stddef.h> outside of our guards to let it handle
re-inclusion itself.

(cherry picked from commit 2950283)

// Note: This include is outside of header guards because we sometimes get included multiple times
// with different defines and the underlying <stddef.h> will know how to deal with that.
#include_next <stddef.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our builds now fail with

In file included from ../../base/allocator/partition_allocator/src\partition_alloc/partition_alloc_base/debug/alias.h:8:
In file included from ../../third_party/libc++/src/include\cstddef:42:
../../third_party/libc++/src/include\stddef.h(31,2): error: #include_next is a language extension [-Werror,-Wgnu-include-next]
   31 | #include_next <stddef.h>
      |  ^
1 error generated.

…presumably because this is now before the #pragma GCC system_header below. Was that an intentional change?

(https://ci.chromium.org/ui/p/chromium/builders/try/win-rel/560031/overview)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, no that's not intentional. Is -Wgnu-include-next enabled by default? Why did we not see this anywhere else?

I can fix that but it would be nice to have a test that fails if we break this again in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@nico nico Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix!

Is -Wgnu-include-next enabled by default?

No. Apparently someone over here thought it'd be good to compile some of our files with -pedantic, which enables -Wgnu. (Other users of libc++ probably do this too.)

Why did we not see this anywhere else?

I don't know how libc++ tests run. Maybe there isn't a test that includes all public headers and builds with -Wall -Wconversion -Wextra -pedantic -Werror? That would be a good test to have.

Does libc++ have any policy which warning levels it's clean at? Probably not really needed since all its headers are supposed to be system headers which disable warnings, so that test could (and probably should?) even use -Weverything with clang.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ldionne added a commit to ldionne/llvm-project that referenced this pull request Apr 9, 2024
As reported in llvm#86843, we must have #pragma GCC system_header before
we use #include_next, otherwise the compiler may not understand that
we're in a system header and may issue a diagnostic for our usage of
tstellar pushed a commit to llvmbot/llvm-project that referenced this pull request Apr 10, 2024
Libc++'s own <stddef.h> is complicated by the need to handle various
platform-specific macros and to support duplicate inclusion. In reality,
we only need to add a declaration of nullptr_t to it, so we can simply
include the underlying <stddef.h> outside of our guards to let it handle
re-inclusion itself.

(cherry picked from commit 2950283)
nico pushed a commit that referenced this pull request Apr 11, 2024
As reported in #86843, we must have #pragma GCC system_header before we
use #include_next, otherwise the compiler may not understand that we're
in a system header and may issue a diagnostic for our usage of
llvmbot pushed a commit to llvmbot/llvm-project that referenced this pull request Apr 11, 2024
As reported in llvm#86843, we must have #pragma GCC system_header before we
use #include_next, otherwise the compiler may not understand that we're
in a system header and may issue a diagnostic for our usage of

(cherry picked from commit 3c4b673)
llvmbot pushed a commit to llvmbot/llvm-project that referenced this pull request Apr 11, 2024
As reported in llvm#86843, we must have #pragma GCC system_header before we
use #include_next, otherwise the compiler may not understand that we're
in a system header and may issue a diagnostic for our usage of

(cherry picked from commit 3c4b673)
@nico
Copy link
Contributor

nico commented Apr 12, 2024

Here's another thing that happened to work before but breaks now (…but somehow only on Windows):

namespace mynamespace {
#include <stddef.h>
}

(Details: https://crbug.com/333934598)

That's of course something you shouldn't do, but since it used to work, there's code out there that does it. Not a ton as far as I can tell, but I thought I'd mention it. (No action needed.)

@ldionne
Copy link
Member Author

ldionne commented Apr 12, 2024

Thanks for the heads up. We actually ran across exactly one instance of this issue downstream and finished root-causing it just this morning. Indeed, I don't think this is something we can or should do anything about, but it will break a very small number of projects indeed.

The symptom we saw was a missing rsize_t typedef but the cause was the same -- stddef.h being included inside a namespace.

Pinging @llvm/libcxx-vendors in case someone comes across a weird issue, this may save you some time to diagnose it.

tstellar pushed a commit to llvmbot/llvm-project that referenced this pull request Apr 16, 2024
As reported in llvm#86843, we must have #pragma GCC system_header before we
use #include_next, otherwise the compiler may not understand that we're
in a system header and may issue a diagnostic for our usage of

(cherry picked from commit 3c4b673)
@pointhex pointhex mentioned this pull request May 7, 2024
@BertalanD
Copy link
Member

@ldionne Just a heads-up (already mentioned this on Discord): this caused GCC to fail to compile with Xcode 16 beta:

../../gcc/value-pointer-equiv.cc:65:43: error: no viable conversion from 'pair<typename __unwrap_ref_decay<long>::type, typename __unwrap_ref_decay<long>::type>' to 'const pair<tree, tree>'
   65 |   const std::pair <tree, tree> m_marker = std::make_pair (NULL, NULL);
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(tree here is a typedef to a pointer type)

Apparently what they do is redefine NULL to nullptr in gcc/system.h which is included in every TU of the compiler:
https://github.com/gcc-mirror/gcc/blob/1ae5fc24e86ecc9e7b60346d9ca2e56f83517bda/gcc/system.h#L1296-L1304

This was added in gcc-mirror/gcc@d59a576 to fix an issue similar to mine. This used to work because gcc/system.h already includes stddef.h further up:

https://github.com/gcc-mirror/gcc/blob/1ae5fc24e86ecc9e7b60346d9ca2e56f83517bda/gcc/system.h#L42-L44

But stddef.h might be included later too, such as through include/obstack.h. And since the include guards were removed from the #include_next in this PR, it changes back the definition of NULL to __null, leading to the above issue.

I posted a patch for this to the GCC mailing list, but they're asking if this can be fixed on the libc++/macOS SDK side. (Louis, is macOS stddef.h overriding NULLs value if it's already defined a deliberate decision?)

@DimitryAndric
Copy link
Collaborator

gcc's code base has more of these tricks. They also poison certain identifiers, causing problems with libc++ headers. See for example: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111632, which is a similar case, caused by C++ standard headers being included after standard identifiers are redefined.

@ldionne
Copy link
Member Author

ldionne commented Jul 12, 2024

@BertalanD I haven't finished digging into this, but this looks like a Clang-builtins-header bug to me. I don't understand why the clang builtin headers would be overriding NULL again, let alone with 0 when we're in C++ mode. Do you agree?

@ldionne
Copy link
Member Author

ldionne commented Jul 15, 2024

I think I have convinced myself that libc++ isn't to blame here. Before this PR, the way libc++ included stddef.h prevented this issue from getting to users whenever they had libc++ headers on the search path (which is most of the time), but fundamentally the problem seems to be happening in the Clang builtin headers.

Reproducer:

cat <<EOF | clang++ -nostdinc++ -std=c++11 -xc++ - -v -fsyntax-only --trace-includes
#define NULL nullptr
#include <stddef.h> // NULL gets redefined inside that header

static_assert(__is_same(decltype(NULL), decltype(nullptr)), "");
EOF

Output:

#include <...> search starts here:
 <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include
 <...>/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include
 <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
 <...>/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)
End of search list.

. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/stddef.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_header_macro.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_ptrdiff_t.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_size_t.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_wchar_t.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_null.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_nullptr_t.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_max_align_t.h
.. <...>/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/16/include/__stddef_offsetof.h

<stdin>:4:15: error: static assertion failed due to requirement '__is_same(long, std::nullptr_t)':
    4 | static_assert(__is_same(decltype(NULL), decltype(nullptr)), "");
      |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Furthermore, if I additionally add -nobuiltininc to the above command-line in order to stop searching in the Clang builtin headers, the NULL macro is not redefined and I get the following header trace:

. <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/stddef.h
.. <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/_types.h
... <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/_types.h
.... <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h
..... <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/_symbol_aliasing.h
..... <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/_posix_availability.h
.... <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/machine/_types.h
..... <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/arm/_types.h
.... <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/_pthread/_pthread_types.h
.. <...>/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/_types/_null.h
etc

Hence, it looks like the Clang builtin headers today completely override any platform-provided <stddef.h> and they exhibit this behavior of always redefining NULL to __null. It certainly seems incorrect to me that the Clang builtin headers are overriding the platform-provided <stddef.h> -- I don't think that behavior is useful.

However, I also think that users are not allowed to define NULL to their liking and I don't think we should try to support that. So basically, IMO we should clarify the situation w.r.t. the role of the Clang builtin headers and determine whether they are currently overstepping on the platform (I think they are), but fundamentally the NULL redefinition example isn't something that should be expected to work (and AFAICT it doesn't work on Linux).

@ldionne
Copy link
Member Author

ldionne commented Jul 15, 2024

CC @AaronBallman ^

@AaronBallman
Copy link
Collaborator

Hence, it looks like the Clang builtin headers today completely override any platform-provided <stddef.h> and they exhibit this behavior of always redefining NULL to __null. It certainly seems incorrect to me that the Clang builtin headers are overriding the platform-provided <stddef.h> -- I don't think that behavior is useful.

The issue is modules (what problem can't modules solve?):

#if !defined(NULL) || !__building_module(_Builtin_stddef)

Even when NULL is already defined, a non-modular build happily redefines NULL for you (https://godbolt.org/z/M5YsvjGfP is an example with the header files removed). Prior to that change, we had: https://github.com/llvm/llvm-project/blame/3e6d56617f43f86d65dba04c94277dc4a40c2a86/clang/lib/Headers/__stddef_null.h and that did work: https://godbolt.org/z/qGKfKj9hf

This seems to be a regression between trunk and Clang 18: https://godbolt.org/z/TKTzKThff

CC @ian-twilightcoder

@ian-twilightcoder
Copy link
Contributor

Hence, it looks like the Clang builtin headers today completely override any platform-provided <stddef.h> and they exhibit this behavior of always redefining NULL to __null. It certainly seems incorrect to me that the Clang builtin headers are overriding the platform-provided <stddef.h> -- I don't think that behavior is useful.

The issue is modules (what problem can't modules solve?):

#if !defined(NULL) || !__building_module(_Builtin_stddef)

Even when NULL is already defined, a non-modular build happily redefines NULL for you (https://godbolt.org/z/M5YsvjGfP is an example with the header files removed). Prior to that change, we had: https://github.com/llvm/llvm-project/blame/3e6d56617f43f86d65dba04c94277dc4a40c2a86/clang/lib/Headers/__stddef_null.h and that did work: https://godbolt.org/z/qGKfKj9hf

This seems to be a regression between trunk and Clang 18: https://godbolt.org/z/TKTzKThff

CC @ian-twilightcoder

I'm not sure how it would affect gcc, but I think this error in clang is probably caused by #90676. Working on a fix.

@ian-twilightcoder
Copy link
Contributor

#99727

@ian-twilightcoder
Copy link
Contributor

Hence, it looks like the Clang builtin headers today completely override any platform-provided <stddef.h> and they exhibit this behavior of always redefining NULL to __null. It certainly seems incorrect to me that the Clang builtin headers are overriding the platform-provided <stddef.h> -- I don't think that behavior is useful.

The issue is modules (what problem can't modules solve?):

#if !defined(NULL) || !__building_module(_Builtin_stddef)

Even when NULL is already defined, a non-modular build happily redefines NULL for you (https://godbolt.org/z/M5YsvjGfP is an example with the header files removed). Prior to that change, we had: https://github.com/llvm/llvm-project/blame/3e6d56617f43f86d65dba04c94277dc4a40c2a86/clang/lib/Headers/__stddef_null.h and that did work: https://godbolt.org/z/qGKfKj9hf
This seems to be a regression between trunk and Clang 18: https://godbolt.org/z/TKTzKThff
CC @ian-twilightcoder

I'm not sure how it would affect gcc, but I think this error in clang is probably caused by #90676. Working on a fix.

I guess it affects building gcc with llvm? It's definitely #90676 that regresses the redefining NULL thing.

ldionne added a commit to ldionne/llvm-project that referenced this pull request Dec 6, 2024
This change has a long history. It was first attempted naively in
https://reviews.llvm.org/D131425, which didn't work because we broke
the ability for code to include e.g. <stdio.h> multiple times and get
different definitions based on the pre-defined macros.

However, in llvm#86843 we managed to simplify <stddef.h> by including
the underlying system header outside of any include guards, which
worked.

This patch applies the same simplification we did to <stddef.h> to
the other headers that currently mention __need_FOO macros explicitly.
ldionne added a commit to ldionne/llvm-project that referenced this pull request Dec 10, 2024
This change has a long history. It was first attempted naively in
https://reviews.llvm.org/D131425, which didn't work because we broke
the ability for code to include e.g. <stdio.h> multiple times and get
different definitions based on the pre-defined macros.

However, in llvm#86843 we managed to simplify <stddef.h> by including
the underlying system header outside of any include guards, which
worked.

This patch applies the same simplification we did to <stddef.h> to
the other headers that currently mention __need_FOO macros explicitly.
ldionne added a commit to ldionne/llvm-project that referenced this pull request Dec 16, 2024
This change has a long history. It was first attempted naively in
https://reviews.llvm.org/D131425, which didn't work because we broke
the ability for code to include e.g. <stdio.h> multiple times and get
different definitions based on the pre-defined macros.

However, in llvm#86843 we managed to simplify <stddef.h> by including
the underlying system header outside of any include guards, which
worked.

This patch applies the same simplification we did to <stddef.h> to
the other headers that currently mention __need_FOO macros explicitly.
ldionne added a commit that referenced this pull request Dec 17, 2024
This change has a long history. It was first attempted naively in
https://reviews.llvm.org/D131425, which didn't work because we broke the
ability for code to include e.g. <stdio.h> multiple times and get
different definitions based on the pre-defined macros.

However, in #86843 we managed to simplify <stddef.h> by including the
underlying system header outside of any include guards, which worked.

This patch applies the same simplification we did to <stddef.h> to the
other headers that currently mention __need_FOO macros explicitly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants