Skip to content

Commit 08681ff

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 07b1aeb commit 08681ff

File tree

7 files changed

+173
-62
lines changed

7 files changed

+173
-62
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: 98 additions & 56 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,119 @@ 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 {
1460+
auto MaybeReenterImportedFile = [&]() -> bool {
14361461
if (!ModulesEnabled)
14371462
return false;
1463+
// Modules add a wrinkle though: what's included isn't necessarily visible.
1464+
// Consider this module.
1465+
// module Example {
1466+
// module A { header "a.h" export * }
1467+
// module B { header "b.h" export * }
1468+
// }
1469+
// b.h includes c.h. The main file includes a.h, which will trigger a module
1470+
// build of Example, and c.h will be included. However, c.h isn't visible to
1471+
// the main file. Normally this is fine, the main file can just include c.h
1472+
// if it needs it. If c.h is in a module, the include will translate into a
1473+
// module import, this function will be skipped, and everything will work as
1474+
// expected. However, if c.h is not in a module (or is `textual`), then this
1475+
// function will run. If c.h is include-once, it will not be entered from
1476+
// the main file and it will still not be visible.
1477+
14381478
// Ensure FileInfo bits are up to date.
14391479
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-
// }
1480+
1481+
// This brings up a subtlety of #import - it's not a very good indicator of
1482+
// include-once. Developers are often unaware of the difference between
1483+
// #include and #import, and tend to use one or the other indiscrimiately.
1484+
// In order to support #include on include-once headers that lack macro
1485+
// guards and `#pragma once` (which is the vast majority of Objective-C
1486+
// headers), if a file is ever included with #import, it's marked as
1487+
// isImport in the HeaderFileInfo and treated as include-once. This allows
1488+
// #include to work in Objective-C.
1489+
// #include <Foundation/Foundation.h>
1490+
// #include <Foundation/NSString.h>
1491+
// Foundation.h has an #import of NSString.h, and so the second #include is
1492+
// skipped even though NSString.h has no `#pragma once` and no macro guard.
14511493
//
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;
1494+
// However, this helpfulness causes problems with modules. If c.h is not an
1495+
// include-once file, but something included it with #import anyway (as is
1496+
// typical in Objective-C code), this include will be skipped and c.h will
1497+
// not be visible. If the file is not `#pragma once`, consider it not
1498+
// include-once if it is a `textual` header in a module.
1499+
return !FileInfo.isPragmaOnce && FileInfo.isTextualModuleHeader;
1500+
// If the include file has a macro guard, then it might still not be
1501+
// re-entered if the controlling macro is visibly defined. e.g. another
1502+
// header in the module being built included this file and local submodule
1503+
// visibility is not enabled.
1504+
1505+
// It might be tempting to re-enter the include-once file if it's not
1506+
// visible in an attempt to make it visible. However this will still cause
1507+
// redeclaration errors against the known-but-not-visible declarations. The
1508+
// include file not being visible will most likely cause "undefined x"
1509+
// errors, but at least there's a slim chance of compilation succeeding.
14691510
};
14701511

1471-
// If this is a #import directive, check that we have not already imported
1472-
// this header.
14731512
if (isImport) {
1474-
// If this has already been imported, don't import it again.
1513+
// As discussed above, record that this file was ever `#import`ed, and treat
1514+
// it as an include-once file from here out.
14751515
FileInfo.isImport = true;
1476-
1477-
// Has this already been #import'ed or #include'd?
1478-
if (PP.alreadyIncluded(File) && !TryEnterImported())
1516+
if (PP.alreadyIncluded(File) && !MaybeReenterImportedFile())
14791517
return false;
14801518
} 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())
1519+
// isPragmaOnce and isImport are only set after the file has been included
1520+
// at least once. If either are set then this is a repeat #include of an
1521+
// include-once file.
1522+
if (FileInfo.isPragmaOnce ||
1523+
(FileInfo.isImport && !MaybeReenterImportedFile()))
14841524
return false;
14851525
}
14861526

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)) {
1527+
// As a final optimization, check for a macro guard and skip entering the file
1528+
// if the controlling macro is defined. The macro guard will effectively erase
1529+
// the file's contents, and the include would have no effect other than to
1530+
// waste time opening and reading a file.
1531+
if (const IdentifierInfo *ControllingMacro =
1532+
FileInfo.getControllingMacro(ExternalLookup)) {
14911533
// 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.
1534+
// defined in that module rather than checking all visible modules. This is
1535+
// mainly to cover corner cases where the same controlling macro is used in
1536+
// different files in multiple modules.
14941537
if (M ? PP.isMacroDefinedInLocalModule(ControllingMacro, M)
14951538
: PP.isMacroDefined(ControllingMacro)) {
14961539
++NumMultiIncludeFileOptzn;
@@ -1499,7 +1542,6 @@ bool HeaderSearch::ShouldEnterIncludeFile(Preprocessor &PP,
14991542
}
15001543

15011544
IsFirstIncludeOfFile = PP.markIncluded(File);
1502-
15031545
return true;
15041546
}
15051547

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 -fmodules-cache-path=%t/no-lsv -fmodules -fimplicit-module-maps -I%t %t/multiple-imports.m -verify
4+
// RUN: %clang_cc1 -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)