Skip to content

Commit 771c074

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 07317bb commit 771c074

File tree

7 files changed

+173
-63
lines changed

7 files changed

+173
-63
lines changed

clang/include/clang/Lex/HeaderSearch.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,17 @@ 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.
8282
LLVM_PREFERRED_TYPE(bool)
8383
unsigned isModuleHeader : 1;
8484

85-
/// Whether this header is part of the module that we are building.
85+
/// Whether this header is a textual header in a module (isModuleHeader will
86+
/// be false)
87+
LLVM_PREFERRED_TYPE(bool)
88+
unsigned isTextualModuleHeader : 1;
89+
90+
/// Whether this header is part of and built with the module that we are
91+
/// building.
8692
LLVM_PREFERRED_TYPE(bool)
8793
unsigned isCompilingModuleHeader : 1;
8894

@@ -128,13 +134,20 @@ struct HeaderFileInfo {
128134

129135
HeaderFileInfo()
130136
: isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User),
131-
External(false), isModuleHeader(false), isCompilingModuleHeader(false),
132-
Resolved(false), IndexHeaderMapHeader(false), IsValid(false) {}
137+
External(false), isModuleHeader(false), isTextualModuleHeader(false),
138+
isCompilingModuleHeader(false), Resolved(false),
139+
IndexHeaderMapHeader(false), IsValid(false) {}
133140

134141
/// Retrieve the controlling macro for this header file, if
135142
/// any.
136143
const IdentifierInfo *
137144
getControllingMacro(ExternalPreprocessorSource *External);
145+
146+
/// Update the module membership bits based on the header role.
147+
///
148+
/// isModuleHeader will potentially be set, but not cleared.
149+
/// isTextualModuleHeader will be set or cleared based on the role update.
150+
void mergeModuleMembership(ModuleMap::ModuleHeaderRole Role);
138151
};
139152

140153
/// An external source of header file information, which may supply
@@ -522,6 +535,9 @@ class HeaderSearch {
522535
///
523536
/// \return false if \#including the file will have no effect or true
524537
/// if we should include it.
538+
///
539+
/// \param M The module to which `File` belongs (this should usually be the
540+
/// SuggestedModule returned by LookupFile/LookupSubframeworkHeader)
525541
bool ShouldEnterIncludeFile(Preprocessor &PP, FileEntryRef File,
526542
bool isImport, bool ModulesEnabled, Module *M,
527543
bool &IsFirstIncludeOfFile);

clang/lib/Lex/HeaderSearch.cpp

Lines changed: 100 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,120 @@ 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;
1419-
HFI.isCompilingModuleHeader |= isCompilingModuleHeader;
1434+
HFI.mergeModuleMembership(Role);
1435+
HFI.isCompilingModuleHeader |=
1436+
(HFI.isModuleHeader && isCompilingModuleHeader);
14201437
}
14211438

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

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

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

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

15011545
IsFirstIncludeOfFile = PP.markIncluded(File);
1502-
15031546
return true;
15041547
}
15051548

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)