Skip to content

Conversation

@wzssyqa
Copy link
Contributor

@wzssyqa wzssyqa commented Dec 19, 2024

The limits.h of glibc, aka /usr/include/limits.h file of *-linux-gnu systems, has #include_next <limits.h>, so the limits.h from clang is included.

And in the limits.h for clang, #include_next <limits.h> is also used.

Normally it won't be a problem as the headers is protected by something like _LIBC_LIMITS_H_, while it may be a problem when we cross-build glibc on a none glibc platform. For example if we build glibc for x86_64-linux-gnu on a x86_64-linux-musl platform.

To test it, we can do

echo "#include </usr/include/limits.h>" | clang-19 -E -O2 -xc - -MM -H

We can see there is

. /usr/include/limits.h
.. /usr/lib/llvm-19/lib/clang/19/include/limits.h
... /usr/include/limits.h

It seems that other libc implementations other than glibc don't have this problem.

@llvmbot llvmbot added clang Clang issues not falling into any other category backend:X86 clang:headers Headers provided by Clang, e.g. for intrinsics labels Dec 19, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 19, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-backend-x86

Author: YunQiang Su (wzssyqa)

Changes

The limits.h of glibc, aka /usr/include/limits.h file of *-linux-gnu systems, has #include_next &lt;limits.h&gt;, so the limits.h from clang is included.

And in the limits.h for clang, #include_next &lt;limits.h&gt; is also used.

Normally it won't be a problem as the headers is protected by something like _LIBC_LIMITS_H_, while it may be a problem when we cross-build glibc on a none glibc platform. For example if we build glibc for x86_64-linux-gnu on a x86_64-linux-musl platform.

To test it, we can do

echo "#include &lt;/usr/include/limits.h&gt;" | bin/clang -E -O2 -xc - -MM -H

We can see there is

. /usr/include/limits.h
.. /usr/lib/llvm-19/lib/clang/19/include/limits.h
... /usr/include/limits.h

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

1 Files Affected:

  • (modified) clang/lib/Headers/limits.h (+3-2)
diff --git a/clang/lib/Headers/limits.h b/clang/lib/Headers/limits.h
index d08227fe4d3d48..90f01791f857ee 100644
--- a/clang/lib/Headers/limits.h
+++ b/clang/lib/Headers/limits.h
@@ -20,8 +20,9 @@
 #endif
 
 /* System headers include a number of constants from POSIX in <limits.h>.
-   Include it if we're hosted. */
-#if __STDC_HOSTED__ && __has_include_next(<limits.h>)
+   Include it if we're hosted. The limits.h of glibc uses include_next to
+   include us, we shouldn't include it again. */
+#if __STDC_HOSTED__ && __has_include_next(<limits.h>) && !defined(_LIBC_LIMITS_H_)
 #include_next <limits.h>
 #endif
 

@wzssyqa
Copy link
Contributor Author

wzssyqa commented Jan 7, 2025

ping

@pirama-arumuga-nainar
Copy link
Collaborator

pirama-arumuga-nainar commented Jan 7, 2025

Does this pattern still occur while doing echo "#include <limits.h>" | clang-19 -E -O2 -xc - -MM -H? That is idiomatic compared to including /usr/include/limits.h directly.

It also doesn't seem correct to have clang's headers depend on the _LIBC_LIMITS_H_ macro which is defined in glibc.

Adding @AaronBallman who will have stronger opinion around this.

@AaronBallman
Copy link
Collaborator

I'm confused why this is necessary, we already work around glibc's quirk here:

#if defined __GNUC__ && !defined _GCC_LIMITS_H_

which should then hit glibc's header guard here:
https://sourceware.org/git/?p=glibc.git;a=blob;f=include/limits.h#l122

@wzssyqa
Copy link
Contributor Author

wzssyqa commented Jan 8, 2025

I'm confused why this is necessary, we already work around glibc's quirk here:

#if defined __GNUC__ && !defined _GCC_LIMITS_H_

which should then hit glibc's header guard here:
https://sourceware.org/git/?p=glibc.git;a=blob;f=include/limits.h#l122

There are two problems:

  • it makes the unittests of glibc unhappy when I am using clang to build glibc.
  • If we build glibc on an none-glibc system, such as x86_64-linux-musl.
    • #include </absolute/path/limits.h> import /usr/lib/llvm-19/lib/clang/19/include/limits.h
    • /usr/lib/llvm-19/lib/clang/19/include/limits.h import /usr/include/limits.h
    • /usr/include/limits.h is from musl, which is not protected and it define duplicated macro with </absolute/path/limits.h>

@AaronBallman
Copy link
Collaborator

Maybe my brain still isn't engaging after the holidays, but I'm still not seeing why this change is correct. From the point when Clang gets involved:

So as best I can tell, this is basically another variant of the same thing we were already doing to avoid re-including (part of) glibc, which is why I don't see how that helps. I would have expected the changes to be something more along the lines of (before the block where we define _GCC_LIMITS_H):

/* When building glibc on a musl libc system, glibc will include
   Clang's limits.h, which will include musl's limits.h, which
   cannot handle being included that way. Avert this #include_next
   madness by skipping musl's header. */
#if defined __GNUC__ && defined _GCC_LIMITS_H_ && !defined _LIMITS_H
#define _LIMITS_H
#endif

but that leads to the question of: why is including musl's limits.h a problem? It redefines macros, but that is fine on its own: https://godbolt.org/z/4nGf8zzxE If glibc is expecting different values, that seems like glibc could run into the issue if musl's header was discoverable on the include paths without involving Clang.

@wzssyqa
Copy link
Contributor Author

wzssyqa commented Jan 14, 2025

You are right. My patch only fix the first problem: making a unit test of glibc happy.
The failure test case of glibc is:

  • check-local-headers

See: #120673 also.

@wzssyqa
Copy link
Contributor Author

wzssyqa commented Jan 17, 2025

but that leads to the question of: why is including musl's limits.h a problem?

If we build glibc on Alpine with clang, we will meet the problem like

./include/bits/xopen_lim.h:84:10: warning: "NL_NMAX" redefined
   84 | # define NL_NMAX        INT_MAX

It's due to that

    glibc/include/limits.h  includes
        /usr/lib/llvm19/lib/clang/19/include/limits.h  includes
            /usr/include/limits.h

The limits.h of glibc, aka /usr/include/limits.h file of *-linux-gnu
systems, has `#include_next <limits.h>`, so the limits.h from clang
is included.

And in the limits.h for clang, `#include_next <limits.h>` is also
used.

Normally it won't be a problem as the headers is protected by something
like _LIBC_LIMITS_H_, while it may be a problem when we cross-build
glibc on a none glibc platform. For example if we build glibc for
x86_64-linux-gnu on a x86_64-linux-musl platform.

To test it, we can do
```
echo "#include </usr/include/limits.h>" | bin/clang -E -O2 -xc - -MM -H
```
We can see there is
```
. /usr/include/limits.h
.. /usr/lib/llvm-19/lib/clang/19/include/limits.h
... /usr/include/limits.h
```
@AaronBallman
Copy link
Collaborator

but that leads to the question of: why is including musl's limits.h a problem?

If we build glibc on Alpine with clang, we will meet the problem like

./include/bits/xopen_lim.h:84:10: warning: "NL_NMAX" redefined
   84 | # define NL_NMAX        INT_MAX

It's due to that

    glibc/include/limits.h  includes
        /usr/lib/llvm19/lib/clang/19/include/limits.h  includes
            /usr/include/limits.h

Yes and no. It's due to the redefinitions, for sure. But that diagnostic is suppressed in system headers: https://godbolt.org/z/Mb7Kh975f, so I think we need to understand why there's a limits.h being included as though it were not a system header, because that's a bug.

@wzssyqa
Copy link
Contributor Author

wzssyqa commented Jan 21, 2025

Yes and no. It's due to the redefinitions, for sure. But that diagnostic is suppressed in system headers: https://godbolt.org/z/Mb7Kh975f, so I think we need to understand why there's a limits.h being included as though it were not a system header, because that's a bug.

As we are building glibc, in which it includes the limits.h of itself, instead of the system one.

@AaronBallman
Copy link
Collaborator

Yes and no. It's due to the redefinitions, for sure. But that diagnostic is suppressed in system headers: https://godbolt.org/z/Mb7Kh975f, so I think we need to understand why there's a limits.h being included as though it were not a system header, because that's a bug.

As we are building glibc, in which it includes the limits.h of itself, instead of the system one.

That's the bug then. It needs to include the header as a system header, not a user header.

@wzssyqa
Copy link
Contributor Author

wzssyqa commented Jan 22, 2025

Yes and no. It's due to the redefinitions, for sure. But that diagnostic is suppressed in system headers: https://godbolt.org/z/Mb7Kh975f, so I think we need to understand why there's a limits.h being included as though it were not a system header, because that's a bug.

As we are building glibc, in which it includes the limits.h of itself, instead of the system one.

That's the bug then. It needs to include the header as a system header, not a user header.

Yes. It does. It uses -I option to include limits.h of itself.
The problem is that
limits.h of glibc includes
limits.h of clang
and
limits.h of clang includes
limits.h of system

@AaronBallman
Copy link
Collaborator

Yes and no. It's due to the redefinitions, for sure. But that diagnostic is suppressed in system headers: https://godbolt.org/z/Mb7Kh975f, so I think we need to understand why there's a limits.h being included as though it were not a system header, because that's a bug.

As we are building glibc, in which it includes the limits.h of itself, instead of the system one.

That's the bug then. It needs to include the header as a system header, not a user header.

Yes. It does. It uses -I option to include limits.h of itself.

Do you get the same behavior when you use -isystem instead?

@wzssyqa
Copy link
Contributor Author

wzssyqa commented Jan 24, 2025

I have a test like with file

/*k.c */
#include <limits.h>
CHAR_BIT

/* include/limits.h */
#ifndef _LIBC_LIMITS_H_
#define _LIBC_LIMITS_H_
#include_next <limits.h>
#define CHAR_BIT 9
#endif

The commands

clang -E -MF - -MM -H -isystem include k.c
clang -E -MF - -MM -H -I include k.c

emit the same error message

. include/limits.h
.. /usr/lib/llvm-19/lib/clang/19/include/limits.h
... /usr/include/limits.h
In file included from k.c:1:
In file included from include/limits.h:3:
In file included from /usr/lib/llvm-19/lib/clang/19/include/limits.h:25:
/usr/include/limits.h:145:5: error: function-like macro '__GLIBC_USE' is not defined
  145 | #if __GLIBC_USE (IEC_60559_BFP_EXT_C23)
      |     ^
/usr/include/limits.h:184:5: error: function-like macro '__GLIBC_USE' is not defined
  184 | #if __GLIBC_USE (ISOC23)
      |     ^
k.o: k.c include/limits.h /usr/lib/llvm-19/lib/clang/19/include/limits.h \
  /usr/include/limits.h
2 errors generated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:X86 clang:headers Headers provided by Clang, e.g. for intrinsics clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants