Skip to content

[interop] Prohibit use of C++ APIs in public interfaces that opt-in i… #65129

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

Merged
merged 1 commit into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3187,7 +3187,8 @@ ERROR(decl_from_hidden_module,none,
"it is an SPI imported from %3|"
"it is SPI|"
"%3 was imported for SPI only|"
"%3 was not imported by this file}4",
"%3 was not imported by this file|"
"C++ types from imported module %3 do not support library evolution}4",
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))
WARNING(decl_from_hidden_module_warn,none,
"cannot use %0 %1 %select{here|as property wrapper here|"
Expand All @@ -3208,7 +3209,8 @@ ERROR(typealias_desugars_to_type_from_hidden_module,none,
"it is an SPI imported from %4|"
"<<ERROR>>|"
"%4 was imported for SPI only|"
"%4 was not imported by this file}5",
"%4 was not imported by this file|"
"C++ types from imported module %4 do not support library evolution}5",
(DeclName, StringRef, StringRef, unsigned, Identifier, unsigned))
ERROR(conformance_from_implementation_only_module,none,
"cannot use conformance of %0 to %1 %select{here|as property wrapper here|"
Expand All @@ -3219,7 +3221,8 @@ ERROR(conformance_from_implementation_only_module,none,
"the conformance is declared as SPI in %3|"
"the conformance is declared as SPI|"
"%3 was imported for SPI only|"
"%3 was not imported by this file}4",
"%3 was not imported by this file|"
"C++ types from imported module %3 do not support library evolution}4",
(Type, Identifier, unsigned, Identifier, unsigned))
NOTE(assoc_conformance_from_implementation_only_module,none,
"in associated type %0 (inferred as %1)", (Type, Type))
Expand Down Expand Up @@ -6290,7 +6293,8 @@ ERROR(inlinable_decl_ref_from_hidden_module,
"it is an SPI imported from %3|"
"it is SPI|"
"%3 was imported for SPI only|"
"%3 was not imported by this file}4",
"%3 was not imported by this file|"
"C++ APIs from imported module %3 do not support library evolution}4",
(DescriptiveDeclKind, DeclName, unsigned, Identifier, unsigned))

WARNING(inlinable_decl_ref_from_hidden_module_warn,
Expand All @@ -6306,7 +6310,8 @@ ERROR(inlinable_typealias_desugars_to_type_from_hidden_module,
"it is an SPI imported from %4|"
"<<ERROR>>|"
"%4 was imported for SPI only|"
"%4 was not imported by this file}5",
"%4 was not imported by this file|"
"C++ types from imported module %4 do not support library evolution}5",
(DeclName, StringRef, StringRef, unsigned, Identifier, unsigned))

NOTE(missing_import_inserted,
Expand Down
69 changes: 69 additions & 0 deletions lib/Sema/TypeCheckAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "swift/AST/Pattern.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/TypeCheckRequests.h"
#include "clang/AST/DeclObjC.h"

using namespace swift;

Expand Down Expand Up @@ -1788,6 +1789,66 @@ class UsableFromInlineChecker : public AccessControlCheckerBase,
}
}
};

bool isFragileClangType(clang::QualType type) {
if (type.isNull())
return true;
auto underlyingTypePtr = type->getUnqualifiedDesugaredType();
// Objective-C types are compatible with library
// evolution.
if (underlyingTypePtr->isObjCObjectPointerType())
return false;
// Builtin clang types are compatible with library evolution.
if (underlyingTypePtr->isBuiltinType())
return false;
// Pointers to non-fragile types are non-fragile.
if (underlyingTypePtr->isPointerType())
return isFragileClangType(underlyingTypePtr->getPointeeType());
return true;
}

bool isFragileClangNode(const ClangNode &node) {
auto *decl = node.getAsDecl();
if (!decl)
return false;
// Namespaces by themselves don't impact ABI.
if (isa<clang::NamespaceDecl>(decl))
return false;
// Objective-C type declarations are compatible with library evolution.
if (isa<clang::ObjCContainerDecl>(decl))
return false;
if (auto *fd = dyn_cast<clang::FunctionDecl>(decl)) {
if (!isa<clang::CXXMethodDecl>(decl) &&
!isFragileClangType(fd->getDeclaredReturnType())) {
for (const auto *param : fd->parameters()) {
if (isFragileClangType(param->getType()))
return true;
}
// A global function whose return and parameter types are compatible with
// library evolution is compatible with library evolution.
return false;
}
}
if (auto *md = dyn_cast<clang::ObjCMethodDecl>(decl)) {
if (!isFragileClangType(md->getReturnType())) {
for (const auto *param : md->parameters()) {
if (isFragileClangType(param->getType()))
return true;
}
// An Objective-C method whose return and parameter types are compatible
// with library evolution is compatible with library evolution.
return false;
}
}
// An Objective-C property whose can be compatible
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor typo: whose -> whose type

// with library evolution if its type is compatible.
if (auto *pd = dyn_cast<clang::ObjCPropertyDecl>(decl))
return isFragileClangType(pd->getType());
if (auto *typedefDecl = dyn_cast<clang::TypedefNameDecl>(decl))
return isFragileClangType(typedefDecl->getUnderlyingType());
return true;
}

} // end anonymous namespace

/// Returns the kind of origin, implementation-only import or SPI declaration,
Expand Down Expand Up @@ -1894,6 +1955,14 @@ swift::getDisallowedOriginKind(const Decl *decl,
DisallowedOriginKind::SPIImported;
}

// C++ APIs do not support library evolution.
if (SF->getASTContext().LangOpts.EnableCXXInterop && where.getDeclContext() &&
where.getDeclContext()->getAsDecl() &&
where.getDeclContext()->getAsDecl()->getModuleContext()->isResilient() &&
decl->hasClangNode() && !decl->getModuleContext()->isSwiftShimsModule() &&
isFragileClangNode(decl->getClangNode()))
return DisallowedOriginKind::FragileCxxAPI;

return DisallowedOriginKind::None;
}

Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckAccess.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum class DisallowedOriginKind : uint8_t {
SPILocal,
SPIOnly,
MissingImport,
FragileCxxAPI,
None
};

Expand Down
10 changes: 9 additions & 1 deletion stdlib/cmake/modules/AddSwiftStdlib.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,7 @@ endfunction()
# [IS_STDLIB]
# [IS_STDLIB_CORE]
# [IS_SDK_OVERLAY]
# [IS_FRAGILE]
# INSTALL_IN_COMPONENT comp
# MACCATALYST_BUILD_FLAVOR flavor
# source1 [source2 source3 ...])
Expand Down Expand Up @@ -709,6 +710,11 @@ endfunction()
#
# MACCATALYST_BUILD_FLAVOR
# Possible values are 'ios-like', 'macos-like', 'zippered', 'unzippered-twin'

# IS_FRAGILE
# Disable library evolution even
# if this library is part of the SDK.

#
# source1 ...
# Sources to add into this library
Expand All @@ -723,7 +729,8 @@ function(add_swift_target_library_single target name)
SHARED
STATIC
NO_LINK_NAME
INSTALL_WITH_SHARED)
INSTALL_WITH_SHARED
IS_FRAGILE)
set(SWIFTLIB_SINGLE_single_parameter_options
ARCHITECTURE
DEPLOYMENT_VERSION_IOS
Expand Down Expand Up @@ -932,6 +939,7 @@ function(add_swift_target_library_single target name)
${SWIFTLIB_SINGLE_IS_STDLIB_keyword}
${SWIFTLIB_SINGLE_IS_STDLIB_CORE_keyword}
${SWIFTLIB_SINGLE_IS_SDK_OVERLAY_keyword}
${SWIFTLIB_SINGLE_IS_FRAGILE_keyword}
${embed_bitcode_arg}
${SWIFTLIB_SINGLE_STATIC_keyword}
${SWIFTLIB_SINGLE_NO_LINK_NAME_keyword}
Expand Down
8 changes: 5 additions & 3 deletions stdlib/cmake/modules/SwiftSource.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function(handle_swift_sources
dependency_sibgen_target_out_var_name
sourcesvar externalvar name)
cmake_parse_arguments(SWIFTSOURCES
"IS_MAIN;IS_STDLIB;IS_STDLIB_CORE;IS_SDK_OVERLAY;EMBED_BITCODE;STATIC;NO_LINK_NAME"
"IS_MAIN;IS_STDLIB;IS_STDLIB_CORE;IS_SDK_OVERLAY;EMBED_BITCODE;STATIC;NO_LINK_NAME;IS_FRAGILE"
"SDK;ARCHITECTURE;INSTALL_IN_COMPONENT;MACCATALYST_BUILD_FLAVOR;BOOTSTRAPPING"
"DEPENDS;COMPILE_FLAGS;MODULE_NAME;ENABLE_LTO"
${ARGN})
Expand All @@ -64,6 +64,7 @@ function(handle_swift_sources
translate_flag(${SWIFTSOURCES_STATIC} "STATIC"
STATIC_arg)
translate_flag(${SWIFTSOURCES_NO_LINK_NAME} "NO_LINK_NAME" NO_LINK_NAME_arg)
translate_flag(${SWIFTSOURCES_IS_FRAGILE} "IS_FRAGILE" IS_FRAGILE_arg)
if(DEFINED SWIFTSOURCES_BOOTSTRAPPING)
set(BOOTSTRAPPING_arg "BOOTSTRAPPING" ${SWIFTSOURCES_BOOTSTRAPPING})
endif()
Expand Down Expand Up @@ -152,6 +153,7 @@ function(handle_swift_sources
${EMBED_BITCODE_arg}
${STATIC_arg}
${BOOTSTRAPPING_arg}
${IS_FRAGILE_arg}
INSTALL_IN_COMPONENT "${SWIFTSOURCES_INSTALL_IN_COMPONENT}"
MACCATALYST_BUILD_FLAVOR "${SWIFTSOURCES_MACCATALYST_BUILD_FLAVOR}")
set("${dependency_target_out_var_name}" "${dependency_target}" PARENT_SCOPE)
Expand Down Expand Up @@ -376,7 +378,7 @@ function(_compile_swift_files
dependency_sib_target_out_var_name dependency_sibopt_target_out_var_name
dependency_sibgen_target_out_var_name)
cmake_parse_arguments(SWIFTFILE
"IS_MAIN;IS_STDLIB;IS_STDLIB_CORE;IS_SDK_OVERLAY;EMBED_BITCODE;STATIC"
"IS_MAIN;IS_STDLIB;IS_STDLIB_CORE;IS_SDK_OVERLAY;EMBED_BITCODE;STATIC;IS_FRAGILE"
"OUTPUT;MODULE_NAME;INSTALL_IN_COMPONENT;MACCATALYST_BUILD_FLAVOR;BOOTSTRAPPING"
"SOURCES;FLAGS;DEPENDS;SDK;ARCHITECTURE;OPT_FLAGS;MODULE_DIR"
${ARGN})
Expand Down Expand Up @@ -487,7 +489,7 @@ function(_compile_swift_files
endif()

# The standard library and overlays are built resiliently when SWIFT_STDLIB_STABLE_ABI=On.
if(SWIFTFILE_IS_STDLIB AND SWIFT_STDLIB_STABLE_ABI)
if(SWIFTFILE_IS_STDLIB AND NOT SWIFTFILE_IS_FRAGILE AND SWIFT_STDLIB_STABLE_ABI)
list(APPEND swift_flags "-enable-library-evolution")
list(APPEND swift_flags "-library-level" "api")
list(APPEND swift_flags "-Xfrontend" "-require-explicit-availability=ignore")
Expand Down
4 changes: 3 additions & 1 deletion stdlib/public/Cxx/std/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ add_dependencies(sdk-overlay libstdcxx-modulemap)
#
# C++ Standard Library Overlay.
#
add_swift_target_library(swiftCxxStdlib STATIC NO_LINK_NAME IS_STDLIB IS_SWIFT_ONLY
# The overlay is fragile (i.e. it does not use library evolution)
# as it's not ABI stable.
add_swift_target_library(swiftCxxStdlib STATIC NO_LINK_NAME IS_STDLIB IS_SWIFT_ONLY IS_FRAGILE
std.swift
String.swift

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// RUN: %empty-directory(%t)
// RUN: split-file %s %t
// RUN: %target-swift-frontend %t/test.swift -I %t/Inputs -typecheck -enable-library-evolution -enable-experimental-cxx-interop -verify

// REQUIRES: objc_interop

//--- Inputs/module.modulemap
module ObjCxxModule {
header "header.h"
requires cplusplus
}

//--- Inputs/header.h

#import <Foundation/Foundation.h>

class CxxStruct {
public:
int x; int y;

void method() const;
};

@interface ObjCClass: NSObject

- (void)myTestMethod;
- (int *)returnsIntPtr;

- (CxxStruct)testMethodReturnsCxxStruct;
- (void)testMethodTakesCxxStructPtr: (CxxStruct * _Nullable) ptr;
+ (ObjCClass * _Nonnull)getInstance;

@property int intProp;
@property(copy) ObjCClass * _Nonnull objcClassProp;
@property CxxStruct * structPtrProp;

@end

using ObjCClassTypealias = ObjCClass * _Nonnull;

@protocol ObjCProto

- (void)testProto;

@end

using QualIDTypeAlias = id<ObjCProto>;

using BuiltinIntTypealis = int;

//--- test.swift

import ObjCxxModule

// ok
public func usesObjCClass() -> ObjCClass {
return ObjCClass.getInstance()
}

public func usesObjCClassTypealias(_ x: ObjCClassTypealias) {
}

public func usesObjCProto(_ x: ObjCProto) {
}

public func usesQualIDTypeAlias(_ x: QualIDTypeAlias) {
}

public func usesBuiltinIntTypealis() -> BuiltinIntTypealis {
return 21
}

@inlinable
public func publicFuncPublicBody() {
let i = ObjCClass.getInstance()
i.myTestMethod()
i.returnsIntPtr()
let _ = i.intProp
let _ = i.objcClassProp
// expected-error@+1 {{instance method 'testMethodReturnsCxxStruct()' cannot be used in an '@inlinable' function because C++ APIs from imported module 'ObjCxxModule' do not support library evolution}}
i.testMethodReturnsCxxStruct()
// expected-error@+1 {{instance method 'testMethodTakesCxxStructPtr' cannot be used in an '@inlinable' function because C++ APIs from imported module 'ObjCxxModule' do not support library evolution}}
i.testMethodTakesCxxStructPtr(nil)
// expected-error@+1 {{property 'structPtrProp' cannot be used in an '@inlinable' function because C++ APIs from imported module 'ObjCxxModule' do not support library evolution}}
let _ = i.structPtrProp
}

// expected-error@+1 {{cannot use struct 'CxxStruct' here; C++ types from imported module 'ObjCxxModule' do not support library evolution}}
public func usesCxxStruct(_ x: CxxStruct) {
}
Loading