Strict Memory Checking C interop Doesn't Work on Android
Description
Strict Memory Checking and the automatic Span conversion when interacting with C code does not work on Android. This works well on macOS and Linux but fails to build with a missing header on Android (as well as on Windows, as reported at https://github.com/swiftlang/swift/issues/86087).
Reproduction
git clone https://github.com/vapor/authentication.git
cd authentication
swift build --swift-sdk aarch64-unknown-linux-android28
This fails with missing headers lifetimebound.h and ptrcheck.h:
Build errors
[490/498] Compiling Authentication BcryptHasher.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
[491/498] Compiling Authentication Data+Array.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
[492/498] Compiling Authentication PlaintextHasher.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
[493/498] Compiling Authentication VaporBcrypt.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
[494/498] Compiling Authentication PasswordHasher.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
error: emit-module command failed with exit code 1 (use -v to see invocation)
[495/498] Emitting module Authentication
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
[496/498] Compiling Authentication BcryptError.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
[497/498] Compiling Authentication Collection+SecureCompare.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
[498/498] Compiling Authentication OTP.swift
/opt/src/github/vapor/authentication/Sources/CVaporAuthBcrypt/include/../bcrypt.h:4:10: error: 'lifetimebound.h' file not found
2 | #include <string.h>
3 | #include <stdio.h>
4 | #include <lifetimebound.h>
| `- error: 'lifetimebound.h' file not found
5 | #include <ptrcheck.h>
6 |
/opt/src/github/vapor/authentication/Sources/Authentication/Passwords/Bcrypt/VaporBcrypt.swift:1:17: error: could not build C module 'CVaporAuthBcrypt'
1 | internal import CVaporAuthBcrypt
| `- error: could not build C module 'CVaporAuthBcrypt'
2 |
3 | #if canImport(FoundationEssentials)
Error: Error terminated(code: 1) running command: /Users/marc/Library/Developer/Toolchains/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-24-a.xctoolchain/usr/bin/swift build --swift-sdk aarch64-unknown-linux-android28 -Xswiftc -DSKIP_BRIDGE -Xswiftc -DTARGET_OS_ANDROID
Expected behavior
The package should build.
Environment
marc@zap authentication % swift --version
Apple Swift version 6.3-dev (LLVM 8322c7222063676, Swift d1735f66f43c37f)
Target: arm64-apple-macosx15.0
Build config: +assertions
marc@zap authentication % skip android build --version
Swift Package Manager - Swift 6.3.0-dev
marc@zap authentication % sw_vers
ProductName: macOS
ProductVersion: 15.7.2
BuildVersion: 24G325
marc@zap authentication % swift sdk list
swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-24-a_android
Additional information
The Swift SDK for Android uses the NDK clang includes, which do not seem to contain lifetimebound.h and ptrcheck.h for some reason.
The diff of the headers in the host toolchain and the NDK toolchain can be seen:
$ ls -1 ~/Library/Developer/Toolchains/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-24-a.xctoolchain/usr/lib/clang/21/include/ > /tmp/clang-host.txt
$ ls -1 ~/Library/org.swift.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-24-a_android.artifactbundle/swift-android/android-ndk-r29/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/21/include/ > /tmp/clang-ndk.txt
$ diff /tmp/clang-*.txt
16,19d15
< __clang_spirv_builtins.h
< __float_float.h
< __float_header_macro.h
< __float_infinity_nan.h
57d52
< andes_vector.h
71d65
< availability_domain.h
117a112
> bits
120d114
< bounds_safety_soft_traps.h
135d128
< feature-availability.h
160d152
< lifetimebound.h
178a171,172
> omp-tools.h
> omp.h
192d185
< ptrcheck.h
219d211
< stdcountof.h
Interestingly, just manually copying the header files over with:
$ cp ~/Library/Developer/Toolchains/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-24-a.xctoolchain/usr/lib/clang/21/include/{ptrcheck.h,lifetimebound.h} ~/Library/org.swift.swiftpm/swift-sdks/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-24-a_android.artifactbundle/swift-android/android-ndk-r29/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/21/include/
does enable the package to be built and all the tests pass.
Until this is fixed, if you own the code you're importing you can work around this by using your own defines:
#if defined(__has_feature) && __has_feature(bounds_attributes)
#define __has_ptrcheck 1
#else
#define __has_ptrcheck 0
#endif
#if defined(__has_feature) && __has_feature(bounds_safety_attributes)
#define __has_bounds_safety_attributes 1
#else
#define __has_bounds_safety_attributes 0
#endif
#if __has_ptrcheck || __has_bounds_safety_attributes
#define __counted_by(N) __attribute__((__counted_by__(N)))
#endif
#if defined(__cplusplus) && defined(__has_cpp_attribute)
#define __use_cpp_spelling(x) __has_cpp_attribute(x)
#else
#define __use_cpp_spelling(x) 0
#endif
#if __use_cpp_spelling(clang::lifetimebound)
#define __lifetimebound [[clang::lifetimebound]]
#else
#define __lifetimebound __attribute__((lifetimebound))
#endif
#if __use_cpp_spelling(clang::lifetime_capture_by)
#define __lifetime_capture_by(X) [[clang::lifetime_capture_by(X)]]
#else
#define __lifetime_capture_by(X) __attribute__((lifetime_capture_by(X)))
#endif
#if __use_cpp_spelling(clang::noescape)
#define __noescape [[clang::noescape]]
#else
#define __noescape __attribute__((noescape))
#endif
I think that the approach for this is to specify -resource-dir to the resource directory that we ship with the compiler. That will ensure that we get the right headers. This is why we have the three "layers" of flags:
-
-resource-dir(compiler resources) -
-sdk(Swift content) -
-sysroot(NDK/system root)
CC @airspeedswift