Skip to content

Commit ce9493d

Browse files
committed
[cxx-interop] Builtin functions should ignore return type nullability to be compatible with C interop
In C interop mode, the return type of builtin functions like 'memcpy' 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 ce9493d

File tree

4 files changed

+49
-0
lines changed

4 files changed

+49
-0
lines changed

lib/ClangImporter/ImportType.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,6 +2223,24 @@ 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 builtin functions like 'memcpy' drops
2227+
// any nullability specifiers from their return type, and preserves it on the
2228+
// declared return type. Thus, in C mode the imported return type of such
2229+
// functions is always optional. However, in C++ interop mode, the return type
2230+
// of builtin functions can preseve the nullability specifiers on their return
2231+
// type, and thus the imported return type of such functions can be
2232+
// non-optional, if the type is annotated with
2233+
// `_Nonnull`. The difference between these two modes can break cross-module
2234+
// Swift serialization, as Swift will no longer be able to resolve an x-ref
2235+
// such as 'memcpy' from a Swift module that uses C interop, within a Swift
2236+
// context that uses C++ interop. In order to avoid the x-ref resolution
2237+
// failure, normalize the return type's nullability for builtin functions in
2238+
// C++ interop mode, to match the imported type in C interop mode.
2239+
if (SwiftContext.LangOpts.EnableCXXInterop && clangDecl->getBuiltinID() &&
2240+
isa<clang::AttributedType>(returnType)) {
2241+
if (cast<clang::AttributedType>(returnType)->getImmediateNullability())
2242+
clang::AttributedType::stripOuterNullability(returnType);
2243+
}
22262244

22272245
// Specialized templates need to match the args/result exactly (i.e.,
22282246
// ptr -> ptr not ptr -> Optional<ptr>).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include <stdint.h>
4+
5+
#ifdef __cplusplus
6+
extern "C" {
7+
#endif
8+
void* _Nonnull memcpy(void* _Nonnull, const void* _Nonnull, size_t);
9+
10+
void* _Nonnull memcpy42(void* _Nonnull, const void* _Nonnull, size_t);
11+
12+
#ifdef __cplusplus
13+
}
14+
#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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -cxx-interoperability-mode=default
2+
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs
3+
4+
import CustomStringBuiltins
5+
6+
public func testMemcpyOptionalReturn(p: UnsafeMutableRawPointer, e: UnsafeRawPointer) {
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+
}

0 commit comments

Comments
 (0)