Skip to content

Commit 7ff2914

Browse files
committed
[Modules] Add Darwin-specific compatibility module map parsing hacks
This preserves backwards compatibility for two hacks in the Darwin system module map files: 1. The use of 'requires excluded' to make headers non-modular, which should really be mapped to 'textual' now that we have this feature. 2. Silently removes a bogus cplusplus requirement from IOKit.avc. Once we start diagnosing missing requirements and headers on auto-imports these would have broken compatibility with existing Darwin SDKs. llvm-svn: 244912
1 parent a747179 commit 7ff2914

File tree

7 files changed

+154
-7
lines changed

7 files changed

+154
-7
lines changed

clang/include/clang/Basic/Module.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,12 @@ class Module {
356356
/// its top-level module.
357357
std::string getFullModuleName() const;
358358

359+
/// \brief Whether the full name of this module is equal to joining
360+
/// \p nameParts with "."s.
361+
///
362+
/// This is more efficient than getFullModuleName().
363+
bool fullModuleNameIs(ArrayRef<StringRef> nameParts) const;
364+
359365
/// \brief Retrieve the top-level module for this (sub)module, which may
360366
/// be this module.
361367
Module *getTopLevelModule() {

clang/lib/Basic/Module.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ std::string Module::getFullModuleName() const {
139139
return Result;
140140
}
141141

142+
bool Module::fullModuleNameIs(ArrayRef<StringRef> nameParts) const {
143+
for (const Module *M = this; M; M = M->Parent) {
144+
if (nameParts.empty() || M->Name != nameParts.back())
145+
return false;
146+
nameParts = nameParts.drop_back();
147+
}
148+
return nameParts.empty();
149+
}
150+
142151
Module::DirectoryName Module::getUmbrellaDir() const {
143152
if (Header U = getUmbrellaHeader())
144153
return {"", U.Entry->getDir()};

clang/lib/Lex/ModuleMap.cpp

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,17 @@ namespace clang {
10151015

10161016
/// \brief The active module.
10171017
Module *ActiveModule;
1018-
1018+
1019+
/// \brief Whether a module uses the 'requires excluded' hack to mark its
1020+
/// contents as 'textual'.
1021+
///
1022+
/// On older Darwin SDK versions, 'requires excluded' is used to mark the
1023+
/// contents of the Darwin.C.excluded (assert.h) and Tcl.Private modules as
1024+
/// non-modular headers. For backwards compatibility, we continue to
1025+
/// support this idiom for just these modules, and map the headers to
1026+
/// 'textual' to match the original intent.
1027+
llvm::SmallPtrSet<Module *, 2> UsesRequiresExcludedHack;
1028+
10191029
/// \brief Consume the current token and return its location.
10201030
SourceLocation consumeToken();
10211031

@@ -1570,6 +1580,38 @@ void ModuleMapParser::parseExternModuleDecl() {
15701580
: File->getDir(), ExternLoc);
15711581
}
15721582

1583+
/// Whether to add the requirement \p Feature to the module \p M.
1584+
///
1585+
/// This preserves backwards compatibility for two hacks in the Darwin system
1586+
/// module map files:
1587+
///
1588+
/// 1. The use of 'requires excluded' to make headers non-modular, which
1589+
/// should really be mapped to 'textual' now that we have this feature. We
1590+
/// drop the 'excluded' requirement, and set \p IsRequiresExcludedHack to
1591+
/// true. Later, this bit will be used to map all the headers inside this
1592+
/// module to 'textual'.
1593+
///
1594+
/// This affects Darwin.C.excluded (for assert.h) and Tcl.Private.
1595+
///
1596+
/// 2. Removes a bogus cplusplus requirement from IOKit.avc. This requirement
1597+
/// was never correct and causes issues now that we check it, so drop it.
1598+
static bool shouldAddRequirement(Module *M, StringRef Feature,
1599+
bool &IsRequiresExcludedHack) {
1600+
static const StringRef DarwinCExcluded[] = {"Darwin", "C", "excluded"};
1601+
static const StringRef TclPrivate[] = {"Tcl", "Private"};
1602+
static const StringRef IOKitAVC[] = {"IOKit", "avc"};
1603+
1604+
if (Feature == "excluded" && (M->fullModuleNameIs(DarwinCExcluded) ||
1605+
M->fullModuleNameIs(TclPrivate))) {
1606+
IsRequiresExcludedHack = true;
1607+
return false;
1608+
} else if (Feature == "cplusplus" && M->fullModuleNameIs(IOKitAVC)) {
1609+
return false;
1610+
}
1611+
1612+
return true;
1613+
}
1614+
15731615
/// \brief Parse a requires declaration.
15741616
///
15751617
/// requires-declaration:
@@ -1605,9 +1647,18 @@ void ModuleMapParser::parseRequiresDecl() {
16051647
std::string Feature = Tok.getString();
16061648
consumeToken();
16071649

1608-
// Add this feature.
1609-
ActiveModule->addRequirement(Feature, RequiredState,
1610-
Map.LangOpts, *Map.Target);
1650+
bool IsRequiresExcludedHack = false;
1651+
bool ShouldAddRequirement =
1652+
shouldAddRequirement(ActiveModule, Feature, IsRequiresExcludedHack);
1653+
1654+
if (IsRequiresExcludedHack)
1655+
UsesRequiresExcludedHack.insert(ActiveModule);
1656+
1657+
if (ShouldAddRequirement) {
1658+
// Add this feature.
1659+
ActiveModule->addRequirement(Feature, RequiredState, Map.LangOpts,
1660+
*Map.Target);
1661+
}
16111662

16121663
if (!Tok.is(MMToken::Comma))
16131664
break;
@@ -1657,9 +1708,16 @@ void ModuleMapParser::parseHeaderDecl(MMToken::TokenKind LeadingToken,
16571708
consumeToken();
16581709
}
16591710
}
1711+
16601712
if (LeadingToken == MMToken::TextualKeyword)
16611713
Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader);
16621714

1715+
if (UsesRequiresExcludedHack.count(ActiveModule)) {
1716+
// Mark this header 'textual' (see doc comment for
1717+
// Module::UsesRequiresExcludedHack).
1718+
Role = ModuleMap::ModuleHeaderRole(Role | ModuleMap::TextualHeader);
1719+
}
1720+
16631721
if (LeadingToken != MMToken::HeaderKeyword) {
16641722
if (!Tok.is(MMToken::HeaderKeyword)) {
16651723
Diags.Report(Tok.getLocation(), diag::err_mmap_expected_header)
@@ -1838,14 +1896,40 @@ void ModuleMapParser::parseUmbrellaDirDecl(SourceLocation UmbrellaLoc) {
18381896
HadError = true;
18391897
return;
18401898
}
1841-
1899+
1900+
if (UsesRequiresExcludedHack.count(ActiveModule)) {
1901+
// Mark this header 'textual' (see doc comment for
1902+
// ModuleMapParser::UsesRequiresExcludedHack). Although iterating over the
1903+
// directory is relatively expensive, in practice this only applies to the
1904+
// uncommonly used Tcl module on Darwin platforms.
1905+
std::error_code EC;
1906+
SmallVector<Module::Header, 6> Headers;
1907+
for (llvm::sys::fs::recursive_directory_iterator I(Dir->getName(), EC), E;
1908+
I != E && !EC; I.increment(EC)) {
1909+
if (const FileEntry *FE = SourceMgr.getFileManager().getFile(I->path())) {
1910+
1911+
Module::Header Header = {I->path(), FE};
1912+
Headers.push_back(std::move(Header));
1913+
}
1914+
}
1915+
1916+
// Sort header paths so that the pcm doesn't depend on iteration order.
1917+
llvm::array_pod_sort(Headers.begin(), Headers.end(),
1918+
[](const Module::Header *A, const Module::Header *B) {
1919+
return A->NameAsWritten.compare(B->NameAsWritten);
1920+
});
1921+
for (auto &Header : Headers)
1922+
Map.addHeader(ActiveModule, std::move(Header), ModuleMap::TextualHeader);
1923+
return;
1924+
}
1925+
18421926
if (Module *OwningModule = Map.UmbrellaDirs[Dir]) {
18431927
Diags.Report(UmbrellaLoc, diag::err_mmap_umbrella_clash)
18441928
<< OwningModule->getFullModuleName();
18451929
HadError = true;
18461930
return;
1847-
}
1848-
1931+
}
1932+
18491933
// Record this umbrella directory.
18501934
Map.setUmbrellaDir(ActiveModule, Dir, DirName);
18511935
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// assert.h
2+
#define DARWIN_C_EXCLUDED 1

clang/test/Modules/Inputs/System/usr/include/module.map

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,25 @@ module uses_other_constants {
3030
header "uses_other_constants.h"
3131
export *
3232
}
33+
34+
module Darwin {
35+
module C {
36+
module excluded {
37+
requires excluded
38+
header "assert.h"
39+
}
40+
}
41+
}
42+
43+
module Tcl {
44+
module Private {
45+
requires excluded
46+
umbrella ""
47+
}
48+
}
49+
50+
module IOKit {
51+
module avc {
52+
requires cplusplus
53+
}
54+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// tcl-private/header.h
2+
#define TCL_PRIVATE 1
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: rm -rf %t
2+
// RUN: %clang_cc1 -fmodules -fmodules-cache-path=%t -fimplicit-module-maps -isystem %S/Inputs/System/usr/include -triple x86_64-apple-darwin10 %s -verify -fsyntax-only
3+
// expected-no-diagnostics
4+
5+
@import Darwin.C.excluded; // no error, header is implicitly 'textual'
6+
@import Tcl.Private; // no error, header is implicitly 'textual'
7+
@import IOKit.avc; // no error, cplusplus requirement removed
8+
9+
#if defined(DARWIN_C_EXCLUDED)
10+
#error assert.h should be textual
11+
#elif defined(TCL_PRIVATE)
12+
#error tcl-private/header.h should be textual
13+
#endif
14+
15+
#import <assert.h>
16+
#import <tcl-private/header.h>
17+
18+
#if !defined(DARWIN_C_EXCLUDED)
19+
#error assert.h missing
20+
#elif !defined(TCL_PRIVATE)
21+
#error tcl-private/header.h missing
22+
#endif

0 commit comments

Comments
 (0)