Skip to content

Commit 4fda7f4

Browse files
committed
[interop][SwiftToCxx] do not expose APIs with imported declarations whose modules do not have a generated header as specified by the user
The frontend option '-clang-header-expose-module' allows the user to specify that APIs from an imported module have been exposed in another generated header, and thus APIs that depend on them can be safely exposed in the current generated header.
1 parent 6bf6c0b commit 4fda7f4

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)