Skip to content

Commit 1cb3d45

Browse files
[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.
1 parent b2ea046 commit 1cb3d45

File tree

7 files changed

+201
-63
lines changed

7 files changed

+201
-63
lines changed

clang/include/clang/Lex/HeaderSearch.h

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,19 @@ struct HeaderFileInfo {
7878
LLVM_PREFERRED_TYPE(bool)
7979
unsigned External : 1;
8080

81-
/// Whether this header is part of a module.
81+
/// Whether this header is part of and built with a module
82+
/// (`isTextualModuleHeader` will be `false`).
8283
LLVM_PREFERRED_TYPE(bool)
8384
unsigned isModuleHeader : 1;
8485

85-
/// Whether this header is part of the module that we are building.
86+
/// Whether this header is a textual header in a module (`isModuleHeader` will
87+
/// be `false`).
88+
LLVM_PREFERRED_TYPE(bool)
89+
unsigned isTextualModuleHeader : 1;
90+
91+
/// Whether this header is part of the module that we are building
92+
/// (independent of `isModuleHeader` and `isTextualModuleHeader`, they can
93+
/// both be `false`).
8694
LLVM_PREFERRED_TYPE(bool)
8795
unsigned isCompilingModuleHeader : 1;
8896

@@ -128,13 +136,20 @@ struct HeaderFileInfo {
128136

129137
HeaderFileInfo()
130138
: isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User),
131-
External(false), isModuleHeader(false), isCompilingModuleHeader(false),
132-
Resolved(false), IndexHeaderMapHeader(false), IsValid(false) {}
139+
External(false), isModuleHeader(false), isTextualModuleHeader(false),
140+
isCompilingModuleHeader(false), Resolved(false),
141+
IndexHeaderMapHeader(false), IsValid(false) {}
133142

134143
/// Retrieve the controlling macro for this header file, if
135144
/// any.
136145
const IdentifierInfo *
137146
getControllingMacro(ExternalPreprocessorSource *External);
147+
148+
/// Update the module membership bits based on the header role.
149+
///
150+
/// isModuleHeader will potentially be set, but not cleared.
151+
/// isTextualModuleHeader will be set or cleared based on the role update.
152+
void mergeModuleMembership(ModuleMap::ModuleHeaderRole Role);
138153
};
139154

140155
/// An external source of header file information, which may supply
@@ -522,6 +537,9 @@ class HeaderSearch {
522537
///
523538
/// \return false if \#including the file will have no effect or true
524539
/// if we should include it.
540+
///
541+
/// \param M The module to which `File` belongs (this should usually be the
542+
/// SuggestedModule returned by LookupFile/LookupSubframeworkHeader)
525543
bool ShouldEnterIncludeFile(Preprocessor &PP, FileEntryRef File,
526544
bool isImport, bool ModulesEnabled, Module *M,
527545
bool &IsFirstIncludeOfFile);

clang/lib/Lex/HeaderSearch.cpp

Lines changed: 126 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,23 @@ OptionalFileEntryRef HeaderSearch::LookupSubframeworkHeader(
13071307
// File Info Management.
13081308
//===----------------------------------------------------------------------===//
13091309

1310+
static void mergeHeaderFileInfoModuleBits(HeaderFileInfo &HFI,
1311+
bool isModuleHeader,
1312+
bool isTextualModuleHeader) {
1313+
assert((!isModuleHeader || !isTextualModuleHeader) &&
1314+
"A header can't build with a module and be textual at the same time");
1315+
HFI.isModuleHeader |= isModuleHeader;
1316+
if (HFI.isModuleHeader)
1317+
HFI.isTextualModuleHeader = false;
1318+
else
1319+
HFI.isTextualModuleHeader |= isTextualModuleHeader;
1320+
}
1321+
1322+
void HeaderFileInfo::mergeModuleMembership(ModuleMap::ModuleHeaderRole Role) {
1323+
mergeHeaderFileInfoModuleBits(*this, ModuleMap::isModular(Role),
1324+
(Role & ModuleMap::TextualHeader));
1325+
}
1326+
13101327
/// Merge the header file info provided by \p OtherHFI into the current
13111328
/// header file info (\p HFI)
13121329
static void mergeHeaderFileInfo(HeaderFileInfo &HFI,
@@ -1315,7 +1332,8 @@ static void mergeHeaderFileInfo(HeaderFileInfo &HFI,
13151332

13161333
HFI.isImport |= OtherHFI.isImport;
13171334
HFI.isPragmaOnce |= OtherHFI.isPragmaOnce;
1318-
HFI.isModuleHeader |= OtherHFI.isModuleHeader;
1335+
mergeHeaderFileInfoModuleBits(HFI, OtherHFI.isModuleHeader,
1336+
OtherHFI.isTextualModuleHeader);
13191337

13201338
if (!HFI.ControllingMacro && !HFI.ControllingMacroID) {
13211339
HFI.ControllingMacro = OtherHFI.ControllingMacro;
@@ -1403,94 +1421,146 @@ bool HeaderSearch::isFileMultipleIncludeGuarded(FileEntryRef File) const {
14031421
void HeaderSearch::MarkFileModuleHeader(FileEntryRef FE,
14041422
ModuleMap::ModuleHeaderRole Role,
14051423
bool isCompilingModuleHeader) {
1406-
bool isModularHeader = ModuleMap::isModular(Role);
1407-
14081424
// Don't mark the file info as non-external if there's nothing to change.
14091425
if (!isCompilingModuleHeader) {
1410-
if (!isModularHeader)
1426+
if ((Role & ModuleMap::ExcludedHeader))
14111427
return;
14121428
auto *HFI = getExistingFileInfo(FE);
14131429
if (HFI && HFI->isModuleHeader)
14141430
return;
14151431
}
14161432

14171433
auto &HFI = getFileInfo(FE);
1418-
HFI.isModuleHeader |= isModularHeader;
1434+
HFI.mergeModuleMembership(Role);
14191435
HFI.isCompilingModuleHeader |= isCompilingModuleHeader;
14201436
}
14211437

14221438
bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP,
14231439
FileEntryRef File, bool isImport,
14241440
bool ModulesEnabled, Module *M,
14251441
bool &IsFirstIncludeOfFile) {
1426-
++NumIncluded; // Count # of attempted #includes.
1427-
1442+
// An include file should be entered if either:
1443+
// 1. This is the first include of the file.
1444+
// 2. This file can be included multiple times, that is it's not an
1445+
// "include-once" file.
1446+
//
1447+
// Include-once is controlled by these preprocessor directives.
1448+
//
1449+
// #pragma once
1450+
// This directive is in the include file, and marks it as an include-once
1451+
// file.
1452+
//
1453+
// #import <file>
1454+
// This directive is in the includer, and indicates that the include file
1455+
// should only be entered if this is the first include.
1456+
++NumIncluded;
14281457
IsFirstIncludeOfFile = false;
1429-
1430-
// Get information about this file.
14311458
HeaderFileInfo &FileInfo = getFileInfo(File);
14321459

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

1471-
// If this is a #import directive, check that we have not already imported
1472-
// this header.
14731539
if (isImport) {
1474-
// If this has already been imported, don't import it again.
1540+
// As discussed above, record that this file was ever `#import`ed, and treat
1541+
// it as an include-once file from here out.
14751542
FileInfo.isImport = true;
1476-
1477-
// Has this already been #import'ed or #include'd?
1478-
if (PP.alreadyIncluded(File) && !TryEnterImported())
1543+
if (PP.alreadyIncluded(File) && !MaybeReenterImportedFile())
14791544
return false;
14801545
} else {
1481-
// Otherwise, if this is a #include of a file that was previously #import'd
1482-
// or if this is the second #include of a #pragma once file, ignore it.
1483-
if ((FileInfo.isPragmaOnce || FileInfo.isImport) && !TryEnterImported())
1546+
// isPragmaOnce and isImport are only set after the file has been included
1547+
// at least once. If either are set then this is a repeat #include of an
1548+
// include-once file.
1549+
if (FileInfo.isPragmaOnce ||
1550+
(FileInfo.isImport && !MaybeReenterImportedFile()))
14841551
return false;
14851552
}
14861553

1487-
// Next, check to see if the file is wrapped with #ifndef guards. If so, and
1488-
// if the macro that guards it is defined, we know the #include has no effect.
1489-
if (const IdentifierInfo *ControllingMacro
1490-
= FileInfo.getControllingMacro(ExternalLookup)) {
1554+
// As a final optimization, check for a macro guard and skip entering the file
1555+
// if the controlling macro is defined. The macro guard will effectively erase
1556+
// the file's contents, and the include would have no effect other than to
1557+
// waste time opening and reading a file.
1558+
if (const IdentifierInfo *ControllingMacro =
1559+
FileInfo.getControllingMacro(ExternalLookup)) {
14911560
// If the header corresponds to a module, check whether the macro is already
1492-
// defined in that module rather than checking in the current set of visible
1493-
// modules.
1561+
// defined in that module rather than checking all visible modules. This is
1562+
// mainly to cover corner cases where the same controlling macro is used in
1563+
// different files in multiple modules.
14941564
if (M ? PP.isMacroDefinedInLocalModule(ControllingMacro, M)
14951565
: PP.isMacroDefined(ControllingMacro)) {
14961566
++NumMultiIncludeFileOptzn;
@@ -1499,7 +1569,6 @@ bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP,
14991569
}
15001570

15011571
IsFirstIncludeOfFile = PP.markIncluded(File);
1502-
15031572
return true;
15041573
}
15051574

clang/lib/Serialization/ASTReader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2094,7 +2094,7 @@ HeaderFileInfoTrait::ReadData(internal_key_ref key, const unsigned char *d,
20942094
Module::Header H = {std::string(key.Filename), "", *FE};
20952095
ModMap.addHeader(Mod, H, HeaderRole, /*Imported=*/true);
20962096
}
2097-
HFI.isModuleHeader |= ModuleMap::isModular(HeaderRole);
2097+
HFI.mergeModuleMembership(HeaderRole);
20982098
}
20992099

21002100
// 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)