Skip to content

Commit b92fcc2

Browse files
committed
[cxx-interop] C library builtin functions from 'string.h' should ignore return type nullability to be compatible with C interop
In C interop mode, the return type of builtin functions like 'memcpy' from headers like 'string.h' drops any nullability specifiers from their return type, and preserves it on the declared return type. Thus, in C mode the imported return type of such functions is always optional. However, in C++ interop mode, the return type of builtin functions can preseve the nullability specifiers on their return type, and thus the imported return type of such functions can be non-optional, if the type is annotated with `_Nonnull`. The difference between these two modes can break cross-module Swift serialization, as Swift will no longer be able to resolve an x-ref such as 'memcpy' from a Swift module that uses C interop, within a Swift context that uses C++ interop. In order to avoid the x-ref resolution failure, normalize the return type's nullability for builtin functions in C++ interop mode, to match the imported type in C interop mode. This fixed an issue when using 'memcpy' from the Android NDK in a x-module context, like between Foundation (that inlines it) and another user module.
1 parent c8b5344 commit b92fcc2

File tree

4 files changed

+77
-0
lines changed

4 files changed

+77
-0
lines changed

lib/ClangImporter/ImportType.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,6 +2223,31 @@ ImportedType ClangImporter::Implementation::importFunctionReturnType(
22232223
if (auto elaborated =
22242224
dyn_cast<clang::ElaboratedType>(returnType))
22252225
returnType = elaborated->desugar();
2226+
// In C interop mode, the return type of library builtin functions
2227+
// like 'memcpy' from headers like 'string.h' drops
2228+
// any nullability specifiers from their return type, and preserves it on the
2229+
// declared return type. Thus, in C mode the imported return type of such
2230+
// functions is always optional. However, in C++ interop mode, the return type
2231+
// of builtin functions can preseve the nullability specifiers on their return
2232+
// type, and thus the imported return type of such functions can be
2233+
// non-optional, if the type is annotated with
2234+
// `_Nonnull`. The difference between these two modes can break cross-module
2235+
// Swift serialization, as Swift will no longer be able to resolve an x-ref
2236+
// such as 'memcpy' from a Swift module that uses C interop, within a Swift
2237+
// context that uses C++ interop. In order to avoid the x-ref resolution
2238+
// failure, normalize the return type's nullability for builtin functions in
2239+
// C++ interop mode, to match the imported type in C interop mode.
2240+
if (SwiftContext.LangOpts.EnableCXXInterop && clangDecl->getBuiltinID() &&
2241+
!clang::Builtin::Context().isTSBuiltin(clangDecl->getBuiltinID()) &&
2242+
clang::Builtin::Context().isPredefinedLibFunction(
2243+
clangDecl->getBuiltinID()) &&
2244+
clang::Builtin::Context().getHeaderName(clangDecl->getBuiltinID()) ==
2245+
StringRef("string.h")) {
2246+
if (const auto ART = dyn_cast<clang::AttributedType>(returnType)) {
2247+
if (ART->getImmediateNullability())
2248+
clang::AttributedType::stripOuterNullability(returnType);
2249+
}
2250+
}
22262251

22272252
// Specialized templates need to match the args/result exactly (i.e.,
22282253
// ptr -> ptr not ptr -> Optional<ptr>).
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
#include <stddef.h>
4+
5+
#define __attribute_pure__ __attribute__((__pure__))
6+
7+
#ifdef __cplusplus
8+
extern "C" {
9+
#endif
10+
void* _Nonnull memcpy(void* _Nonnull, const void* _Nonnull, size_t);
11+
12+
void* _Nonnull memcpy42(void* _Nonnull, const void* _Nonnull, size_t);
13+
14+
void* _Nullable memchr(const void* _Nonnull __s, int __ch, size_t __n) __attribute_pure__;
15+
16+
void* _Nonnull memmove(void* _Nonnull __dst, const void* _Nonnull __src, size_t __n);
17+
18+
void* _Nonnull memset(void* _Nonnull __dst, int __ch, size_t __n);
19+
20+
char* _Nullable strrchr(const char* _Nonnull __s, int __ch) __attribute_pure__;
21+
22+
char* _Nonnull strcpy(char* _Nonnull __dst, const char* _Nonnull __src);
23+
char* _Nonnull strcat(char* _Nonnull __dst, const char* _Nonnull __src);
24+
25+
#ifdef __cplusplus
26+
}
27+
#endif

test/Interop/Cxx/function/Inputs/module.modulemap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ module DefaultArguments {
22
header "default-arguments.h"
33
export *
44
}
5+
6+
module CustomStringBuiltins {
7+
header "custom-string-builtins.h"
8+
export *
9+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource) -typecheck -verify -verify-ignore-unknown -I %S/Inputs -cxx-interoperability-mode=default %s
2+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource) -typecheck -verify -verify-ignore-unknown -I %S/Inputs %s
3+
4+
import CustomStringBuiltins
5+
6+
public func testMemcpyOptionalReturn(p: UnsafeMutableRawPointer, e: UnsafeRawPointer, c: UnsafePointer<CChar>, pc: UnsafeMutablePointer<CChar>) {
7+
// This 'memcpy' is a builtin and is always an optional, regardless of _Nonnull.
8+
let x = CustomStringBuiltins.memcpy(p, e, 1)!
9+
10+
// Not a builtin, _Nonnull makes it a non-optional.
11+
let y = CustomStringBuiltins.memcpy42(p, e, 1)! // expected-error {{cannot force unwrap value of non-optional type 'UnsafeMutableRawPointer'}}
12+
13+
// other builtins from 'string.h'
14+
let _ = CustomStringBuiltins.memchr(e, 42, 1)!
15+
let _ = CustomStringBuiltins.memmove(p, e, 42)!
16+
let _ = CustomStringBuiltins.memset(p, 1, 42)!
17+
let _ = CustomStringBuiltins.strrchr(c, 0)!
18+
let _ = CustomStringBuiltins.strcpy(pc, c)!
19+
let _ = CustomStringBuiltins.strcat(pc, c)!
20+
}

0 commit comments

Comments
 (0)