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

[SR-6772] Swift packages do not correctly respect _GNU_SOURCE declaration when it is present in imported modules. #49321

Closed
Lukasa opened this issue Jan 16, 2018 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior.

Comments

@Lukasa
Copy link
Contributor

Lukasa commented Jan 16, 2018

Previous ID SR-6772
Radar None
Original Reporter @Lukasa
Type Bug
Status Resolved
Resolution Invalid

Attachment: Download

Environment

Linux 4f8db028d077 4.9.60-linuxkit-aufs #1 SMP Mon Nov 6 16:00:12 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Swift version 4.0.2 (swift-4.0.2-RELEASE)
Target: x86_64-unknown-linux-gnu

Additional Detail from JIRA
Votes 1
Component/s
Labels Bug
Assignee None
Priority Medium

md5: c23c4bef4a8c3b0104ebda413e1a3594

Issue Description:

I'm attempting to use some symbols from glibc that are normally hidden behind a _GNU_SOURCE macro definition. To do this, I've written a small SwiftPM module that provides a C header file that includes sys/socket.h after defining _GNU_SOURCE, to make the symbols available from that module to Swift.

Unfortunately, it seems like something in the Swift build toolchain, possibly the clang importer, is getting this wrong. To demonstrate the problem I've attached a small SwiftPM repro package that defines two modules: CMmesg, the C shim, and MMesg, a Swift module that attempts to use the symbols.

Example breakdown

For this example I'm using the mmsghdr structure defined in socket.h on Linux.

The project ultimately consists of three files. Firstly, a simple header file:

#ifndef C_MMESG_H
#define C_MMESG_H

#define _GNU_SOURCE
#include <sys/socket.h>

struct mmsghdr *allocateMMsg();
struct mmsghdr returnMMsg();

#endif

The main goal of this header file is to bring in the types from sys/socket.h, but we also forward-declare the two functions in the following C file. The C file exists to prove that, when compiling just the C code, SwiftPM correctly applies _GNU_SOURCE. It looks like this:

#include <c_mmesg.h>
#include <stdlib.h>

struct mmsghdr *allocateMMsg() {
    return malloc(sizeof(struct mmsghdr));
}


struct mmsghdr returnMMsg() {
    struct mmsghdr t = { 0 };
    return t;
}

This demonstrates that the C code is capable of seeing the structure definition for struct mmsghdr, as it uses it as a concrete type and knows its size.

Then we have a simple bit of Swift code:

import CMmesg

let x = mmsghdr()

print(x)

This code attempts to create a struct mmsghdr and print it.

Error

The compile fails with:

# swift run
Compile CMmesg empty.c
Compile Swift Module 'MMesg' (1 sources)
/code/Sources/MMesg/main.swift:3:9: error: use of unresolved identifier 'mmsghdr'
let x = mmsghdr()
        ^~~~~~~
SwiftGlibc.cmsghdr:1:15: note: did you mean 'cmsghdr'?
public struct cmsghdr {
              ^
SwiftGlibc.msghdr:1:15: note: did you mean 'msghdr'?
public struct msghdr {
              ^
error: terminated(1): /usr/bin/swift-build-tool -f /code/.build/debug.yaml mmesg.exe

Exploration

The initial problem is that the Swift compiler does not seem to see the structure definitions behind _GNU_SOURCE. You can play about with the Swift program to demonstrate this in a few ways. For example, if you change main.swift to instead call allocateMMsg, you'll find that this works, but Swift returns an OpaquePointer, indicating the compiler does not know the size of the type. If you try to call returnMMsg the Swift compiler will complain that it doesn't know what this function is, indicating that rather than barf on the use of the concrete type it's simply decided to elide the symbol definition altogether.

If you change from calling swift run to swift run -Xcc -D_GNU_SOURCE, the compile will start succeeding and it'll see the type. If you use swift run -Xswiftc -D_GNU_SOURCE the compile will not succeed.

Ramifications

In order to using anything from glibc that is hidden behind _GNU_SOURCE there are only two options. Either have all users pass -Xcc -D_GNU_SOURCE when building the tool, or use a C shim module that never exposes the missing types concretely, either by providing its own types or by forward-declaring them.

This can be done, but it's a substantial annoyance.

More generally, this shows a lack of sane handling of C header files. In this case the ramifications are minor, but with this code it's possible that a C module and its corresponding Swift module may end up disagreeing on the size or memory layout of structures, potentially violating the memory safety that Swift is supposed to provide.

@belkadan
Copy link
Contributor

The whole point of modules is that they do not affect how other headers are interpreted. That would defeat the purpose. But SR-360 says we should consider just turning this on all the time.

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 16, 2018

Ok, I understand that argument. I have some questions about it.

1. Perhaps this is just me not understanding clang modules, but why does passing -Xcc -D_GNU_SOURCE change the behaviour?
2. Is it the position of the Swift team/language that Swift will never support providing these kinds of C-header-modifying {{#define}}s for a Swift module? Because _GNU_SOURCE is not the only such macro. At the very least there is everything in man 7 feature_test_macros (https://linux.die.net/man/7/feature_test_macros), but this is a not-uncommon practice in the wider C community.

@belkadan
Copy link
Contributor

You can read more about modules on the Clang website: http://clang.llvm.org/docs/Modules.html. The entire purpose, independent of Swift, is to make it so that headers won't step on each other. It's okay to do it from the command line because then things are consistent across the entire compilation.

@Lukasa
Copy link
Contributor Author

Lukasa commented Jan 17, 2018

So my current workaround involves defining a C module as a shim. It has a header file:

#include <sys/socket.h>

typedef struct {
    struct msghdr msg_hdr;
    unsigned int msg_len;
} CShim_mmsghdr;

int CShim_sendmmsg(int sockfd, CShim_mmsghdr *msgvec, unsigned int vlen, int flags);
int CShim_recvmmsg(int sockfd, CShim_mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout);

And one C file:

#define _GNU_SOURCE
#include <c_shim.h>

int CShim_sendmmsg(int sockfd, CShim_mmsghdr *msgvec, unsigned int vlen, int flags) {
    return sendmmsg(sockfd, (struct mmsghdr *)msgvec, vlen, flags);
}

int CShim_recvmmsg(int sockfd, CShim_mmsghdr *msgvec, unsigned int vlen, int flags, struct timespec *timeout) {
    return recvmmsg(sockfd, (struct mmsghdr *)msgvec, vlen, flags, timeout);
}

The Swift code imports this module and sees the regular sys/socket.h (as per the clang module system), but also sees these two shim functions and the new shim structure. The C code, however, sees the customised sys/socket.h and so can bridge into the appropriate libc code.

This workaround works just fine (except for the UB I'm invoking with the pointer cast). Is this considered an appropriate workaround for this behaviour?

@belkadan
Copy link
Contributor

Yep, that's a totally fine workaround. Sorry it's not better.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior.
Projects
None yet
Development

No branches or pull requests

2 participants