Skip to content

Commit e090fa5

Browse files
[cherry-pick swift/release/6.0][clang][modules] Headers meant to be included multiple times can be completely invisible in clang module builds
Once a file has been `#import`'ed, it gets stamped as if it was `#pragma once` and will not be re-entered, even on #include. This means that any errant #import of a file designed to be included multiple times, such as <assert.h>, will incorrectly mark it as include-once and break the multiple include functionality. Normally this isn't a big problem, e.g. <assert.h> can't have its NDEBUG mode changed after the first #import, but it is still mostly functional. However, when clang modules are involved, this can cause the header to be hidden entirely. Objective-C code most often uses #import for everything, because it's required for most Objective-C headers to prevent double inclusion and redeclaration errors. (It's rare for Objective-C headers to use macro guards or `#pragma once`.) The problem arises when a submodule includes a multiple-include header. The "already included" state is global across all modules (which is necessary so that non-modular headers don't get compiled into multiple translation units and cause redeclaration errors). If another module or the main file #import's the same header, it becomes invisible from then on. If the original submodule is not imported, the include of the header will effectively do nothing and the header will be invisible. The only way to actually get the header's declarations is to somehow figure out which submodule consumed the header, and import that instead. That's basically impossible since it depends on exactly which modules were built in which order. #import is a poor indicator of whether a header is actually include-once, as the #import is external to the header it applies to, and requires that all inclusions correctly and consistently use #import vs #include. When modules are enabled, consider a header marked `textual` in its module as a stronger indicator of multiple-include than #import's indication of include-once. This will allow headers like <assert.h> to always be included when modules are enabled, even if #import is erroneously used somewhere. rdar://123790578 (cherry picked from commit fd02ed8)
1 parent 2ff2ebe commit e090fa5

File tree

7 files changed

+200
-63
lines changed

7 files changed

+200
-63
lines changed

clang/include/clang/Lex/HeaderSearch.h

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,17 @@ struct HeaderFileInfo {
7474
/// and has not changed since.
7575
unsigned External : 1;
7676

77-
/// Whether this header is part of a module.
77+
/// Whether this header is part of and built with a module. i.e. it is listed
78+
/// in a module map, and is not `excluded` or `textual`. (same meaning as
79+
/// `ModuleMap::isModular()`).
7880
unsigned isModuleHeader : 1;
7981

80-
/// Whether this header is part of the module that we are building.
82+
/// Whether this header is a `textual header` in a module.
83+
unsigned isTextualModuleHeader : 1;
84+
85+
/// Whether this header is part of the module that we are building, even if it
86+
/// doesn't build with the module. i.e. this will include `excluded` and
87+
/// `textual` headers as well as normal headers.
8188
unsigned isCompilingModuleHeader : 1;
8289

8390
/// Whether this structure is considered to already have been
@@ -119,13 +126,20 @@ struct HeaderFileInfo {
119126

120127
HeaderFileInfo()
121128
: isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User),
122-
External(false), isModuleHeader(false), isCompilingModuleHeader(false),
123-
Resolved(false), IndexHeaderMapHeader(false), IsValid(false) {}
129+
External(false), isModuleHeader(false), isTextualModuleHeader(false),
130+
isCompilingModuleHeader(false), Resolved(false),
131+
IndexHeaderMapHeader(false), IsValid(false) {}
124132

125133
/// Retrieve the controlling macro for this header file, if
126134
/// any.
127135
const IdentifierInfo *
128136
getControllingMacro(ExternalPreprocessorSource *External);
137+
138+
/// Update the module membership bits based on the header role.
139+
///
140+
/// isModuleHeader will potentially be set, but not cleared.
141+
/// isTextualModuleHeader will be set or cleared based on the role update.
142+
void mergeModuleMembership(ModuleMap::ModuleHeaderRole Role);
129143
};
130144

131145
/// An external source of header file information, which may supply
@@ -516,6 +530,9 @@ class HeaderSearch {
516530
///
517531
/// \return false if \#including the file will have no effect or true
518532
/// if we should include it.
533+
///
534+
/// \param M The module to which `File` belongs (this should usually be the
535+
/// SuggestedModule returned by LookupFile/LookupSubframeworkHeader)
519536
bool ShouldEnterIncludeFile(Preprocessor &PP, const FileEntry *File,
520537
bool isImport, bool ModulesEnabled, Module *M,
521538
bool &IsFirstIncludeOfFile);

clang/lib/Lex/HeaderSearch.cpp

Lines changed: 126 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,23 @@ OptionalFileEntryRef HeaderSearch::LookupSubframeworkHeader(
13141314
// File Info Management.
13151315
//===----------------------------------------------------------------------===//
13161316

1317+
static void mergeHeaderFileInfoModuleBits(HeaderFileInfo &HFI,
1318+
bool isModuleHeader,
1319+
bool isTextualModuleHeader) {
1320+
assert((!isModuleHeader || !isTextualModuleHeader) &&
1321+
"A header can't build with a module and be textual at the same time");
1322+
HFI.isModuleHeader |= isModuleHeader;
1323+
if (HFI.isModuleHeader)
1324+
HFI.isTextualModuleHeader = false;
1325+
else
1326+
HFI.isTextualModuleHeader |= isTextualModuleHeader;
1327+
}
1328+
1329+
void HeaderFileInfo::mergeModuleMembership(ModuleMap::ModuleHeaderRole Role) {
1330+
mergeHeaderFileInfoModuleBits(*this, ModuleMap::isModular(Role),
1331+
(Role & ModuleMap::TextualHeader));
1332+
}
1333+
13171334
/// Merge the header file info provided by \p OtherHFI into the current
13181335
/// header file info (\p HFI)
13191336
static void mergeHeaderFileInfo(HeaderFileInfo &HFI,
@@ -1322,7 +1339,8 @@ static void mergeHeaderFileInfo(HeaderFileInfo &HFI,
13221339

13231340
HFI.isImport |= OtherHFI.isImport;
13241341
HFI.isPragmaOnce |= OtherHFI.isPragmaOnce;
1325-
HFI.isModuleHeader |= OtherHFI.isModuleHeader;
1342+
mergeHeaderFileInfoModuleBits(HFI, OtherHFI.isModuleHeader,
1343+
OtherHFI.isTextualModuleHeader);
13261344

13271345
if (!HFI.ControllingMacro && !HFI.ControllingMacroID) {
13281346
HFI.ControllingMacro = OtherHFI.ControllingMacro;
@@ -1411,94 +1429,146 @@ bool HeaderSearch::isFileMultipleIncludeGuarded(const FileEntry *File) const {
14111429
void HeaderSearch::MarkFileModuleHeader(const FileEntry *FE,
14121430
ModuleMap::ModuleHeaderRole Role,
14131431
bool isCompilingModuleHeader) {
1414-
bool isModularHeader = ModuleMap::isModular(Role);
1415-
14161432
// Don't mark the file info as non-external if there's nothing to change.
14171433
if (!isCompilingModuleHeader) {
1418-
if (!isModularHeader)
1434+
if ((Role & ModuleMap::ExcludedHeader))
14191435
return;
14201436
auto *HFI = getExistingFileInfo(FE);
14211437
if (HFI && HFI->isModuleHeader)
14221438
return;
14231439
}
14241440

14251441
auto &HFI = getFileInfo(FE);
1426-
HFI.isModuleHeader |= isModularHeader;
1442+
HFI.mergeModuleMembership(Role);
14271443
HFI.isCompilingModuleHeader |= isCompilingModuleHeader;
14281444
}
14291445

14301446
bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP,
14311447
const FileEntry *File, bool isImport,
14321448
bool ModulesEnabled, Module *M,
14331449
bool &IsFirstIncludeOfFile) {
1434-
++NumIncluded; // Count # of attempted #includes.
1435-
1450+
// An include file should be entered if either:
1451+
// 1. This is the first include of the file.
1452+
// 2. This file can be included multiple times, that is it's not an
1453+
// "include-once" file.
1454+
//
1455+
// Include-once is controlled by these preprocessor directives.
1456+
//
1457+
// #pragma once
1458+
// This directive is in the include file, and marks it as an include-once
1459+
// file.
1460+
//
1461+
// #import <file>
1462+
// This directive is in the includer, and indicates that the include file
1463+
// should only be entered if this is the first include.
1464+
++NumIncluded;
14361465
IsFirstIncludeOfFile = false;
1437-
1438-
// Get information about this file.
14391466
HeaderFileInfo &FileInfo = getFileInfo(File);
14401467

1441-
// FIXME: this is a workaround for the lack of proper modules-aware support
1442-
// for #import / #pragma once
1443-
auto TryEnterImported = [&]() -> bool {
1444-
if (!ModulesEnabled)
1468+
auto MaybeReenterImportedFile = [&]() -> bool {
1469+
// Modules add a wrinkle though: what's included isn't necessarily visible.
1470+
// Consider this module.
1471+
// module Example {
1472+
// module A { header "a.h" export * }
1473+
// module B { header "b.h" export * }
1474+
// }
1475+
// b.h includes c.h. The main file includes a.h, which will trigger a module
1476+
// build of Example, and c.h will be included. However, c.h isn't visible to
1477+
// the main file. Normally this is fine, the main file can just include c.h
1478+
// if it needs it. If c.h is in a module, the include will translate into a
1479+
// module import, this function will be skipped, and everything will work as
1480+
// expected. However, if c.h is not in a module (or is `textual`), then this
1481+
// function will run. If c.h is include-once, it will not be entered from
1482+
// the main file and it will still not be visible.
1483+
1484+
// If modules aren't enabled then there's no visibility issue. Always
1485+
// respect `#pragma once`.
1486+
if (!ModulesEnabled || FileInfo.isPragmaOnce)
14451487
return false;
1488+
14461489
// Ensure FileInfo bits are up to date.
14471490
ModMap.resolveHeaderDirectives(File);
1448-
// Modules with builtins are special; multiple modules use builtins as
1449-
// modular headers, example:
1450-
//
1451-
// module stddef { header "stddef.h" export * }
1452-
//
1453-
// After module map parsing, this expands to:
1454-
//
1455-
// module stddef {
1456-
// header "/path_to_builtin_dirs/stddef.h"
1457-
// textual "stddef.h"
1458-
// }
1491+
1492+
// This brings up a subtlety of #import - it's not a very good indicator of
1493+
// include-once. Developers are often unaware of the difference between
1494+
// #include and #import, and tend to use one or the other indiscrimiately.
1495+
// In order to support #include on include-once headers that lack macro
1496+
// guards and `#pragma once` (which is the vast majority of Objective-C
1497+
// headers), if a file is ever included with #import, it's marked as
1498+
// isImport in the HeaderFileInfo and treated as include-once. This allows
1499+
// #include to work in Objective-C.
1500+
// #include <Foundation/Foundation.h>
1501+
// #include <Foundation/NSString.h>
1502+
// Foundation.h has an #import of NSString.h, and so the second #include is
1503+
// skipped even though NSString.h has no `#pragma once` and no macro guard.
14591504
//
1460-
// It's common that libc++ and system modules will both define such
1461-
// submodules. Make sure cached results for a builtin header won't
1462-
// prevent other builtin modules from potentially entering the builtin
1463-
// header. Note that builtins are header guarded and the decision to
1464-
// actually enter them is postponed to the controlling macros logic below.
1465-
bool TryEnterHdr = false;
1466-
if (FileInfo.isCompilingModuleHeader && FileInfo.isModuleHeader)
1467-
TryEnterHdr = ModMap.isBuiltinHeader(File);
1468-
1469-
// Textual headers can be #imported from different modules. Since ObjC
1470-
// headers find in the wild might rely only on #import and do not contain
1471-
// controlling macros, be conservative and only try to enter textual headers
1472-
// if such macro is present.
1473-
if (FileInfo.isCompilingModuleHeader && !FileInfo.isModuleHeader &&
1474-
FileInfo.getControllingMacro(ExternalLookup))
1475-
TryEnterHdr = true;
1476-
return TryEnterHdr;
1505+
// However, this helpfulness causes problems with modules. If c.h is not an
1506+
// include-once file, but something included it with #import anyway (as is
1507+
// typical in Objective-C code), this include will be skipped and c.h will
1508+
// not be visible. Consider it not include-once if it is a `textual` header
1509+
// in a module.
1510+
if (FileInfo.isTextualModuleHeader)
1511+
return true;
1512+
1513+
if (FileInfo.isCompilingModuleHeader) {
1514+
// It's safer to re-enter a file whose module is being built because its
1515+
// declarations will still be scoped to a single module.
1516+
if (FileInfo.isModuleHeader) {
1517+
// Headers marked as "builtin" are covered by the system module maps
1518+
// rather than the builtin ones. Some versions of the Darwin module fail
1519+
// to mark stdarg.h and stddef.h as textual. Attempt to re-enter these
1520+
// files while building their module to allow them to function properly.
1521+
if (ModMap.isBuiltinHeader(File))
1522+
return true;
1523+
} else {
1524+
// Files that are excluded from their module can potentially be
1525+
// re-entered from their own module. This might cause redeclaration
1526+
// errors if another module saw this file first, but there's a
1527+
// reasonable chance that its module will build first. However if
1528+
// there's no controlling macro, then trust the #import and assume this
1529+
// really is an include-once file.
1530+
if (FileInfo.getControllingMacro(ExternalLookup))
1531+
return true;
1532+
}
1533+
}
1534+
// If the include file has a macro guard, then it might still not be
1535+
// re-entered if the controlling macro is visibly defined. e.g. another
1536+
// header in the module being built included this file and local submodule
1537+
// visibility is not enabled.
1538+
1539+
// It might be tempting to re-enter the include-once file if it's not
1540+
// visible in an attempt to make it visible. However this will still cause
1541+
// redeclaration errors against the known-but-not-visible declarations. The
1542+
// include file not being visible will most likely cause "undefined x"
1543+
// errors, but at least there's a slim chance of compilation succeeding.
1544+
return false;
14771545
};
14781546

1479-
// If this is a #import directive, check that we have not already imported
1480-
// this header.
14811547
if (isImport) {
1482-
// If this has already been imported, don't import it again.
1548+
// As discussed above, record that this file was ever `#import`ed, and treat
1549+
// it as an include-once file from here out.
14831550
FileInfo.isImport = true;
1484-
1485-
// Has this already been #import'ed or #include'd?
1486-
if (PP.alreadyIncluded(File) && !TryEnterImported())
1551+
if (PP.alreadyIncluded(File) && !MaybeReenterImportedFile())
14871552
return false;
14881553
} else {
1489-
// Otherwise, if this is a #include of a file that was previously #import'd
1490-
// or if this is the second #include of a #pragma once file, ignore it.
1491-
if ((FileInfo.isPragmaOnce || FileInfo.isImport) && !TryEnterImported())
1554+
// isPragmaOnce and isImport are only set after the file has been included
1555+
// at least once. If either are set then this is a repeat #include of an
1556+
// include-once file.
1557+
if (FileInfo.isPragmaOnce ||
1558+
(FileInfo.isImport && !MaybeReenterImportedFile()))
14921559
return false;
14931560
}
14941561

1495-
// Next, check to see if the file is wrapped with #ifndef guards. If so, and
1496-
// if the macro that guards it is defined, we know the #include has no effect.
1497-
if (const IdentifierInfo *ControllingMacro
1498-
= FileInfo.getControllingMacro(ExternalLookup)) {
1562+
// As a final optimization, check for a macro guard and skip entering the file
1563+
// if the controlling macro is defined. The macro guard will effectively erase
1564+
// the file's contents, and the include would have no effect other than to
1565+
// waste time opening and reading a file.
1566+
if (const IdentifierInfo *ControllingMacro =
1567+
FileInfo.getControllingMacro(ExternalLookup)) {
14991568
// If the header corresponds to a module, check whether the macro is already
1500-
// defined in that module rather than checking in the current set of visible
1501-
// modules.
1569+
// defined in that module rather than checking all visible modules. This is
1570+
// mainly to cover corner cases where the same controlling macro is used in
1571+
// different files in multiple modules.
15021572
if (M ? PP.isMacroDefinedInLocalModule(ControllingMacro, M)
15031573
: PP.isMacroDefined(ControllingMacro)) {
15041574
++NumMultiIncludeFileOptzn;
@@ -1507,7 +1577,6 @@ bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP,
15071577
}
15081578

15091579
IsFirstIncludeOfFile = PP.markIncluded(File);
1510-
15111580
return true;
15121581
}
15131582

clang/lib/Serialization/ASTReader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2086,7 +2086,7 @@ HeaderFileInfoTrait::ReadData(internal_key_ref key, const unsigned char *d,
20862086
Module::Header H = {std::string(key.Filename), "", *FE};
20872087
ModMap.addHeader(Mod, H, HeaderRole, /*Imported=*/true);
20882088
}
2089-
HFI.isModuleHeader |= ModuleMap::isModular(HeaderRole);
2089+
HFI.mergeModuleMembership(HeaderRole);
20902090
}
20912091

20922092
// This HeaderFileInfo was externally loaded.

clang/test/Modules/builtin-import.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// RUN: rm -rf %t
2+
// RUN: %clang_cc1 -fsyntax-only -nobuiltininc -nostdinc++ -isysroot %S/Inputs/libc-libcxx/sysroot -isystem %S/Inputs/libc-libcxx/sysroot/usr/include/c++/v1 -isystem %S/Inputs/libc-libcxx/sysroot/usr/include -std=c++11 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -x objective-c++ %s
3+
// RUN: rm -rf %t
24
// RUN: %clang_cc1 -fsyntax-only -nobuiltininc -nostdinc++ -isysroot %S/Inputs/libc-libcxx/sysroot -isystem %S/Inputs/libc-libcxx/sysroot/usr/include/c++/v1 -isystem %S/Inputs/libc-libcxx/sysroot/usr/include -std=c++11 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -x objective-c++ -fmodules-local-submodule-visibility %s
35

46
#include <stdio.h>

clang/test/Modules/import-textual-noguard.mm

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// RUN: rm -rf %t
2+
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -fmodules -fimplicit-module-maps -I%S/Inputs/import-textual/M2 -fmodules-cache-path=%t -x objective-c++ %s -verify
3+
// RUN: rm -rf %t
24
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -fmodules -fimplicit-module-maps -I%S/Inputs/import-textual/M2 -fmodules-cache-path=%t -x objective-c++ -fmodules-local-submodule-visibility %s -verify
35

4-
#include "A/A.h" // expected-error {{could not build module 'M'}}
6+
// expected-no-diagnostics
7+
8+
#include "A/A.h"
59
#include "B/B.h"
610

711
typedef aint xxx;

clang/test/Modules/import-textual.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// RUN: rm -rf %t
2+
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -fmodules -fimplicit-module-maps -I%S/Inputs/import-textual/M -fmodules-cache-path=%t -x objective-c++ %s -verify
3+
// RUN: rm -rf %t
24
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -fmodules -fimplicit-module-maps -I%S/Inputs/import-textual/M -fmodules-cache-path=%t -x objective-c++ -fmodules-local-submodule-visibility %s -verify
35

46
// expected-no-diagnostics

clang/test/Modules/multiple-import.m

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: rm -rf %t
2+
// RUN: split-file %s %t
3+
// RUN: %clang_cc1 -std=c17 -fmodules-cache-path=%t/no-lsv -fmodules -fimplicit-module-maps -I%t %t/multiple-imports.m -verify
4+
// RUN: %clang_cc1 -std=c17 -fmodules-cache-path=%t/lsv -fmodules -fimplicit-module-maps -fmodules-local-submodule-visibility -I%t %t/multiple-imports.m -verify
5+
6+
//--- multiple-imports.m
7+
// expected-no-diagnostics
8+
#import <one.h>
9+
#import <assert.h>
10+
void test(void) {
11+
assert(0);
12+
}
13+
14+
//--- module.modulemap
15+
module Submodules [system] {
16+
module one {
17+
header "one.h"
18+
export *
19+
}
20+
module two {
21+
header "two.h"
22+
export *
23+
}
24+
}
25+
26+
module libc [system] {
27+
textual header "assert.h"
28+
}
29+
30+
//--- one.h
31+
#ifndef one_h
32+
#define one_h
33+
#endif
34+
35+
//--- two.h
36+
#ifndef two_h
37+
#define two_h
38+
#include <assert.h>
39+
#endif
40+
41+
//--- assert.h
42+
#undef assert
43+
#define assert(expression) ((void)0)

0 commit comments

Comments
 (0)