Skip to content

Commit c787349

Browse files
authored
Merge pull request #64290 from hyp/eng/fix-interop-xmodule-imports
[interop][SwiftToCxx] do not expose APIs with imported declarations w…
2 parents fa3059e + 4fda7f4 commit c787349

12 files changed

+162
-48
lines changed

include/swift/Frontend/FrontendOptions.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,16 @@ class FrontendOptions {
413413
/// header.
414414
llvm::Optional<ClangHeaderExposeBehavior> ClangHeaderExposedDecls;
415415

416+
struct ClangHeaderExposedImportedModule {
417+
std::string moduleName;
418+
std::string headerName;
419+
};
420+
421+
/// Indicates which imported modules have a generated header associated with
422+
/// them that can be imported into the generated header for the current
423+
/// module.
424+
std::vector<ClangHeaderExposedImportedModule> clangHeaderExposedImports;
425+
416426
/// \return true if the given action only parses without doing other compilation steps.
417427
static bool shouldActionOnlyParse(ActionType);
418428

include/swift/Option/FrontendOptions.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,12 @@ def clang_header_expose_decls:
11061106
Flags<[FrontendOption, HelpHidden]>,
11071107
MetaVarName<"all-public|has-expose-attr">;
11081108

1109+
def clang_header_expose_module:
1110+
Separate<["-"], "clang-header-expose-module">,
1111+
HelpText<"Allow the compiler to assume that APIs from the specified module are exposed to C/C++/Objective-C in another generated header, so that APIs in the current module that depend on declarations from the specified module can be exposed in the generated header.">,
1112+
Flags<[FrontendOption, HelpHidden]>,
1113+
MetaVarName<"<imported-module-name>=<generated-header-name>">;
1114+
11091115
def weak_link_at_target :
11101116
Flag<["-"], "weak-link-at-target">,
11111117
HelpText<"Weakly link symbols for declarations that were introduced at the "

lib/Frontend/ArgsToFrontendOptionsConverter.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,16 @@ bool ArgsToFrontendOptionsConverter::convert(
313313
HasExposeAttrOrImplicitDeps)
314314
.Default(llvm::None);
315315
}
316-
316+
for (const auto &arg :
317+
Args.getAllArgValues(options::OPT_clang_header_expose_module)) {
318+
auto splitArg = StringRef(arg).split('=');
319+
if (splitArg.second.empty()) {
320+
continue;
321+
}
322+
Opts.clangHeaderExposedImports.push_back(
323+
{splitArg.first.str(), splitArg.second.str()});
324+
}
325+
317326
Opts.StrictImplicitModuleContext = Args.hasArg(OPT_strict_implicit_module_context,
318327
OPT_no_strict_implicit_module_context,
319328
false);

lib/PrintAsClang/DeclAndTypePrinter.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2779,10 +2779,21 @@ static bool excludeForObjCImplementation(const ValueDecl *VD) {
27792779
return false;
27802780
}
27812781

2782+
static bool isExposedToThisModule(const ModuleDecl &M, const ValueDecl *VD,
2783+
const llvm::StringSet<> &exposedModules) {
2784+
if (VD->hasClangNode())
2785+
return true;
2786+
auto *mc = VD->getModuleContext();
2787+
if (mc == &M || mc->isStdlibModule())
2788+
return true;
2789+
return exposedModules.count(mc->getName().str());
2790+
}
2791+
27822792
bool DeclAndTypePrinter::shouldInclude(const ValueDecl *VD) {
27832793
return !VD->isInvalid() && (!requiresExposedAttribute || hasExposeAttr(VD)) &&
27842794
(outputLang == OutputLanguageMode::Cxx
27852795
? cxx_translation::isVisibleToCxx(VD, minRequiredAccess) &&
2796+
isExposedToThisModule(M, VD, exposedModules) &&
27862797
cxx_translation::isExposableToCxx(VD)
27872798
: isVisibleToObjC(VD, minRequiredAccess)) &&
27882799
!VD->getAttrs().hasAttribute<ImplementationOnlyAttr>() &&

lib/PrintAsClang/DeclAndTypePrinter.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "swift/AST/Type.h"
1919
// for OptionalTypeKind
2020
#include "swift/ClangImporter/ClangImporter.h"
21+
#include "llvm/ADT/StringSet.h"
2122

2223
namespace clang {
2324
class NamedDecl;
@@ -48,6 +49,7 @@ class DeclAndTypePrinter {
4849
SwiftToClangInteropContext &interopContext;
4950
AccessLevel minRequiredAccess;
5051
bool requiresExposedAttribute;
52+
llvm::StringSet<> &exposedModules;
5153
OutputLanguageMode outputLang;
5254

5355
/// The name 'CFTypeRef'.
@@ -64,13 +66,14 @@ class DeclAndTypePrinter {
6466
PrimitiveTypeMapping &typeMapping,
6567
SwiftToClangInteropContext &interopContext,
6668
AccessLevel access, bool requiresExposedAttribute,
69+
llvm::StringSet<> &exposedModules,
6770
OutputLanguageMode outputLang)
6871
: M(mod), os(out), prologueOS(prologueOS),
6972
outOfLineDefinitionsOS(outOfLineDefinitionsOS), delayedMembers(delayed),
7073
typeMapping(typeMapping), interopContext(interopContext),
7174
minRequiredAccess(access),
7275
requiresExposedAttribute(requiresExposedAttribute),
73-
outputLang(outputLang) {}
76+
exposedModules(exposedModules), outputLang(outputLang) {}
7477

7578
SwiftToClangInteropContext &getInteropContext() { return interopContext; }
7679

lib/PrintAsClang/ModuleContentsWriter.cpp

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,13 @@ class ModuleWriter {
140140
ModuleWriter(raw_ostream &os, raw_ostream &prologueOS,
141141
llvm::SmallPtrSetImpl<ImportModuleTy> &imports, ModuleDecl &mod,
142142
SwiftToClangInteropContext &interopContext, AccessLevel access,
143-
bool requiresExposedAttribute, OutputLanguageMode outputLang)
143+
bool requiresExposedAttribute, llvm::StringSet<> &exposedModules,
144+
OutputLanguageMode outputLang)
144145
: os(os), imports(imports), M(mod),
145146
outOfLineDefinitionsOS(outOfLineDefinitions),
146147
printer(M, os, prologueOS, outOfLineDefinitionsOS, delayedMembers,
147148
typeMapping, interopContext, access, requiresExposedAttribute,
148-
outputLang),
149+
exposedModules, outputLang),
149150
outputLangMode(outputLang) {}
150151

151152
PrimitiveTypeMapping &getTypeMapping() { return typeMapping; }
@@ -190,11 +191,15 @@ class ModuleWriter {
190191
}
191192

192193
if (outputLangMode == OutputLanguageMode::Cxx) {
193-
// Only add C++ imports in C++ mode for now.
194-
if (!D->hasClangNode())
195-
return true;
194+
// Do not expose compiler private '_ObjC' module.
196195
if (otherModule->getName().str() == CLANG_HEADER_MODULE_NAME)
197196
return true;
197+
// Add C++ module imports in C++ mode explicitly, to ensure that their
198+
// import is always emitted in the header.
199+
if (D->hasClangNode()) {
200+
if (auto *clangMod = otherModule->findUnderlyingClangModule())
201+
imports.insert(clangMod);
202+
}
198203
}
199204

200205
imports.insert(otherModule);
@@ -778,15 +783,16 @@ void swift::printModuleContentsAsObjC(
778783
raw_ostream &os, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
779784
ModuleDecl &M, SwiftToClangInteropContext &interopContext) {
780785
llvm::raw_null_ostream prologueOS;
786+
llvm::StringSet<> exposedModules;
781787
ModuleWriter(os, prologueOS, imports, M, interopContext, getRequiredAccess(M),
782-
/*requiresExposedAttribute=*/false, OutputLanguageMode::ObjC)
788+
/*requiresExposedAttribute=*/false, exposedModules,
789+
OutputLanguageMode::ObjC)
783790
.write();
784791
}
785792

786793
EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx(
787-
raw_ostream &os,
788-
ModuleDecl &M, SwiftToClangInteropContext &interopContext,
789-
bool requiresExposedAttribute) {
794+
raw_ostream &os, ModuleDecl &M, SwiftToClangInteropContext &interopContext,
795+
bool requiresExposedAttribute, llvm::StringSet<> &exposedModules) {
790796
std::string moduleContentsBuf;
791797
llvm::raw_string_ostream moduleOS{moduleContentsBuf};
792798
std::string modulePrologueBuf;
@@ -804,7 +810,7 @@ EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx(
804810
// FIXME: Use getRequiredAccess once @expose is supported.
805811
ModuleWriter writer(moduleOS, prologueOS, info.imports, M, interopContext,
806812
AccessLevel::Public, requiresExposedAttribute,
807-
OutputLanguageMode::Cxx);
813+
exposedModules, OutputLanguageMode::Cxx);
808814
writer.write();
809815
info.dependsOnStandardLibrary = writer.isStdlibRequired();
810816
if (M.isStdlibModule()) {

lib/PrintAsClang/ModuleContentsWriter.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "swift/Basic/LLVM.h"
1818
#include "llvm/ADT/PointerUnion.h"
1919
#include "llvm/ADT/SmallPtrSet.h"
20+
#include "llvm/ADT/StringSet.h"
2021

2122
namespace clang {
2223
class Module;
@@ -46,10 +47,9 @@ struct EmittedClangHeaderDependencyInfo {
4647
/// Prints the declarations of \p M to \p os in C++ language mode.
4748
///
4849
/// \returns Dependencies required by this module.
49-
EmittedClangHeaderDependencyInfo printModuleContentsAsCxx(raw_ostream &os,
50-
ModuleDecl &M,
51-
SwiftToClangInteropContext &interopContext,
52-
bool requiresExposedAttribute);
50+
EmittedClangHeaderDependencyInfo printModuleContentsAsCxx(
51+
raw_ostream &os, ModuleDecl &M, SwiftToClangInteropContext &interopContext,
52+
bool requiresExposedAttribute, llvm::StringSet<> &exposedModules);
5353

5454
} // end namespace swift
5555

lib/PrintAsClang/PrintAsClang.cpp

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,18 @@ static void writePrologue(raw_ostream &out, ASTContext &ctx,
197197
}
198198

199199
static int compareImportModulesByName(const ImportModuleTy *left,
200-
const ImportModuleTy *right) {
200+
const ImportModuleTy *right, bool isCxx) {
201201
auto *leftSwiftModule = left->dyn_cast<ModuleDecl *>();
202202
auto *rightSwiftModule = right->dyn_cast<ModuleDecl *>();
203203

204204
if (leftSwiftModule && !rightSwiftModule)
205-
return -compareImportModulesByName(right, left);
205+
return -compareImportModulesByName(right, left, isCxx);
206206

207207
if (leftSwiftModule && rightSwiftModule)
208208
return leftSwiftModule->getName().compare(rightSwiftModule->getName());
209209

210210
auto *leftClangModule = left->get<const clang::Module *>();
211-
assert(leftClangModule->isSubModule() &&
211+
assert((isCxx || leftClangModule->isSubModule()) &&
212212
"top-level modules should use a normal swift::ModuleDecl");
213213
if (rightSwiftModule) {
214214
// Because the Clang module is a submodule, its full name will never be
@@ -222,7 +222,7 @@ static int compareImportModulesByName(const ImportModuleTy *left,
222222
}
223223

224224
auto *rightClangModule = right->get<const clang::Module *>();
225-
assert(rightClangModule->isSubModule() &&
225+
assert((isCxx || rightClangModule->isSubModule()) &&
226226
"top-level modules should use a normal swift::ModuleDecl");
227227

228228
SmallVector<StringRef, 8> leftReversePath(
@@ -363,12 +363,13 @@ static void collectClangModuleHeaderIncludes(
363363
}
364364
}
365365

366-
static void writeImports(raw_ostream &out,
367-
llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
368-
ModuleDecl &M, StringRef bridgingHeader,
369-
const FrontendOptions &frontendOpts,
370-
clang::HeaderSearch &clangHeaderSearchInfo,
371-
bool useCxxImport = false) {
366+
static void
367+
writeImports(raw_ostream &out, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
368+
ModuleDecl &M, StringRef bridgingHeader,
369+
const FrontendOptions &frontendOpts,
370+
clang::HeaderSearch &clangHeaderSearchInfo,
371+
const llvm::StringMap<StringRef> &exposedModuleHeaderNames,
372+
bool useCxxImport = false) {
372373
// Note: we can't use has_feature(modules) as it's always enabled in C++20
373374
// mode.
374375
out << "#if __has_feature(objc_modules)\n";
@@ -380,8 +381,11 @@ static void writeImports(raw_ostream &out,
380381
// Sort alphabetically for determinism and consistency.
381382
SmallVector<ImportModuleTy, 8> sortedImports{imports.begin(),
382383
imports.end()};
383-
llvm::array_pod_sort(sortedImports.begin(), sortedImports.end(),
384-
&compareImportModulesByName);
384+
std::stable_sort(
385+
sortedImports.begin(), sortedImports.end(),
386+
[&](const ImportModuleTy &left, const ImportModuleTy &right) -> bool {
387+
return compareImportModulesByName(&left, &right, useCxxImport) < 0;
388+
});
385389

386390
auto isUnderlyingModule = [&M, bridgingHeader](ModuleDecl *import) -> bool {
387391
if (bridgingHeader.empty())
@@ -395,7 +399,9 @@ static void writeImports(raw_ostream &out,
395399
clang::FileSystemOptions fileSystemOptions;
396400
clang::FileManager fileManager{fileSystemOptions};
397401

398-
llvm::SmallSet<llvm::SmallString<128>, 10> requiredTextualIncludes;
402+
llvm::SmallSet<llvm::SmallString<128>, 10>
403+
requiredTextualIncludes; // Only included without modules.
404+
llvm::SmallVector<StringRef, 1> textualIncludes; // always included.
399405
llvm::SmallSet<const clang::Module *, 10> visitedModules;
400406
llvm::SmallSet<llvm::SmallString<128>, 10> includeDirs;
401407

@@ -438,6 +444,14 @@ static void writeImports(raw_ostream &out,
438444
useCxxImport ? "#pragma clang module import" : "@import";
439445
for (auto import : sortedImports) {
440446
if (auto *swiftModule = import.dyn_cast<ModuleDecl *>()) {
447+
if (useCxxImport) {
448+
// Do not import Swift modules into the C++ section of the generated
449+
// header unless explicitly exposed.
450+
auto it = exposedModuleHeaderNames.find(swiftModule->getName().str());
451+
if (it != exposedModuleHeaderNames.end())
452+
textualIncludes.push_back(it->getValue());
453+
continue;
454+
}
441455
auto Name = swiftModule->getName();
442456
if (isUnderlyingModule(swiftModule)) {
443457
includeUnderlying = true;
@@ -463,7 +477,7 @@ static void writeImports(raw_ostream &out,
463477
}
464478
} else {
465479
const auto *clangModule = import.get<const clang::Module *>();
466-
assert(clangModule->isSubModule() &&
480+
assert((useCxxImport || clangModule->isSubModule()) &&
467481
"top-level modules should use a normal swift::ModuleDecl");
468482
out << importDirective << ' ';
469483
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
@@ -484,6 +498,9 @@ static void writeImports(raw_ostream &out,
484498
}
485499
}
486500
out << "#endif\n\n";
501+
for (const auto header : textualIncludes) {
502+
out << "#include <" << header << ">\n";
503+
}
487504

488505
if (includeUnderlying) {
489506
if (bridgingHeader.empty())
@@ -548,8 +565,9 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
548565
printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext);
549566
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
550567
emitObjCConditional(os, [&] {
568+
llvm::StringMap<StringRef> exposedModuleHeaderNames;
551569
writeImports(os, imports, *M, bridgingHeader, frontendOpts,
552-
clangHeaderSearchInfo);
570+
clangHeaderSearchInfo, exposedModuleHeaderNames);
553571
});
554572
writePostImportPrologue(os, *M);
555573
emitObjCConditional(os, [&] { os << objcModuleContents.str(); });
@@ -559,6 +577,11 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
559577
M->DeclContext::getASTContext().LangOpts.EnableCXXInterop;
560578
if (!enableCxx)
561579
return;
580+
581+
llvm::StringSet<> exposedModules;
582+
for (const auto &mod : frontendOpts.clangHeaderExposedImports)
583+
exposedModules.insert(mod.moduleName);
584+
562585
// Include the shim header only in the C++ mode.
563586
ClangSyntaxPrinter(os).printIncludeForShimHeader(
564587
"_SwiftCxxInteroperability.h");
@@ -586,10 +609,14 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
586609
llvm::raw_string_ostream moduleContents{moduleContentsBuf};
587610
auto deps = printModuleContentsAsCxx(
588611
moduleContents, *M, interopContext,
589-
/*requiresExposedAttribute=*/requiresExplicitExpose);
612+
/*requiresExposedAttribute=*/requiresExplicitExpose, exposedModules);
590613
// FIXME: In ObjC++ mode, we do not need to reimport duplicate modules.
614+
llvm::StringMap<StringRef> exposedModuleHeaderNames;
615+
for (const auto &mod : frontendOpts.clangHeaderExposedImports)
616+
exposedModuleHeaderNames.insert({mod.moduleName, mod.headerName});
591617
writeImports(os, deps.imports, *M, bridgingHeader, frontendOpts,
592-
clangHeaderSearchInfo, /*useCxxImport=*/true);
618+
clangHeaderSearchInfo, exposedModuleHeaderNames,
619+
/*useCxxImport=*/true);
593620
// Embed the standard library directly.
594621
if (addStdlibDepsInline && deps.dependsOnStandardLibrary) {
595622
assert(!M->isStdlibModule());
@@ -598,9 +625,9 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M,
598625
auto macroGuard = computeMacroGuard(M->getASTContext().getStdlibModule());
599626
os << "#ifndef " << macroGuard << "\n";
600627
os << "#define " << macroGuard << "\n";
601-
printModuleContentsAsCxx(os, *M->getASTContext().getStdlibModule(),
602-
interopContext,
603-
/*requiresExposedAttribute=*/true);
628+
printModuleContentsAsCxx(
629+
os, *M->getASTContext().getStdlibModule(), interopContext,
630+
/*requiresExposedAttribute=*/true, exposedModules);
604631
os << "#endif // " << macroGuard << "\n";
605632
}
606633

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend %S/Inputs/structs.swift -module-name Structs -emit-module -emit-module-path %t/Structs.swiftmodule
3+
4+
// RUN: %target-swift-frontend %s -typecheck -module-name UsesStructs -I %t -clang-header-expose-decls=all-public -emit-clang-header-path %t/uses-structs.h
5+
6+
// RUN: %check-interop-cxx-header-in-clang(%t/uses-structs.h)
7+
// RUN: %FileCheck %s < %t/uses-structs.h
8+
9+
import Structs
10+
11+
public struct StructExposed {
12+
public func availableInHeader() -> Int {
13+
return 0
14+
}
15+
16+
public func unavailableInHeader(_ y: StructSeveralI64) -> StructSeveralI64 {
17+
return y
18+
}
19+
20+
public let unavailableInHeaderProp: StructSeveralI64
21+
}
22+
23+
public func unavailableInHeaderFunc(_ x: StructSeveralI64) -> StructSeveralI64 {
24+
return Structs.passThroughStructSeveralI64(i: 0, x, j: 2)
25+
}
26+
27+
// CHECK-NOT: unavailableInHeaderFunc

test/Interop/SwiftToCxx/cross-module-refs/imported-enum-refs-in-cxx.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-swift-frontend %S/Inputs/enums.swift -module-name Enums -emit-module -emit-module-path %t/Enums.swiftmodule -clang-header-expose-decls=all-public -emit-clang-header-path %t/enums.h
33

4-
// RUN: %target-swift-frontend %s -typecheck -module-name UsesEnums -I %t -clang-header-expose-decls=all-public -emit-clang-header-path %t/uses-enums.h
5-
6-
// FIXME: add import automatically?
7-
// RUN: echo '#include "enums.h"' > %t/fixed-uses-enums.h
8-
// RUN: cat %t/uses-enums.h >> %t/fixed-uses-enums.h
4+
// RUN: %target-swift-frontend %s -typecheck -module-name UsesEnums -I %t -clang-header-expose-decls=all-public -emit-clang-header-path %t/uses-enums.h -clang-header-expose-module Enums=enums.h
95

106
// RUN: %FileCheck %s < %t/uses-enums.h
117

12-
// RUN: %check-interop-cxx-header-in-clang(%t/fixed-uses-enums.h)
8+
// RUN: %check-interop-cxx-header-in-clang(-I %t %t/uses-enums.h)
139

1410
import Enums
1511

0 commit comments

Comments
 (0)