Skip to content

Commit 52906b2

Browse files
Teemperorvgvassilev
authored andcommitted
[cxxmodules] Error when building ROOT dict implicitly
rootcling should never implicitly build a module that is associated with a ROOT dict as such a module needs the rootcling transformations (e.g. to preserve the source code comments for class member as annotated attributes). The only implicitly built modules should be system libraries that don't need those transformations. To inform the user that this shouldn't happen we add a new clang diagnostic client that listens for the -Rmodule-build remarks and verifies that every module built this way is a system module. The reason for choosing a diag client is that clang is building those modules in a very isolated environment that doesn't leak any other information out that we could use to diagnose this. The only supported way clang is informing clients about implicit module builds seems to be the -Rmodule-build remarks.
1 parent 5a479bd commit 52906b2

File tree

2 files changed

+126
-14
lines changed

2 files changed

+126
-14
lines changed

build/unix/module.modulemap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
// FIXME: This should be separated because it introduces header dependency
44
// libThread->libCore->libThread. We should figure out a way to implement it in
55
// a more reasonable way.
6-
module ThreadLocalStorage {
6+
module ThreadLocalStorage [system] {
77
header "ThreadLocalStorage.h"
88
export *
99
}
1010
// These can be used in C mode.
11-
module ROOT_Types {
11+
module ROOT_Types [system] {
1212
module "RtypesCore.h" { header "RtypesCore.h" export * }
1313
module "ESTLType.h" { header "ESTLType.h" export * }
1414
// FIXME: This module should contain only header files with types.
@@ -34,7 +34,7 @@ module ROOT_Types {
3434
// Rtypes.h however has an optional dependency on rtti unless one defines
3535
// R__NO_INLINE_CLASSDEF. We therefore keep two versions of this module: One
3636
// with rtti and one without rtti.
37-
module ROOT_Core_Config_C {
37+
module ROOT_Core_Config_C [system] {
3838
//requires cplusplus
3939

4040
exclude header "RtypesImp.h"
@@ -63,9 +63,9 @@ module ROOT_Core_Config_C {
6363
// when there is no folder GL or contents in it.
6464
// module ROOT_Glew {
6565
// Depending on the platform we get some of these three installed.
66-
module "glew.h" { header "GL/glew.h" export * }
67-
module "wglew.h" { header "GL/wglew.h" export * }
68-
module "glxew.h" { header "GL/glxew.h" export * }
66+
module "glew.h" [system] { header "GL/glew.h" export * }
67+
module "wglew.h" [system] { header "GL/wglew.h" export * }
68+
module "glxew.h" [system] { header "GL/glxew.h" export * }
6969
// link "lib/libGLEW.so"
7070
//}
7171

core/dictgen/src/rootcling_impl.cxx

Lines changed: 120 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ const char *rootClingHelp =
225225
#include "clang/Basic/MemoryBufferCache.h"
226226
#include "clang/Frontend/CompilerInstance.h"
227227
#include "clang/Frontend/FrontendActions.h"
228+
#include "clang/Frontend/FrontendDiagnostic.h"
228229
#include "clang/Lex/HeaderSearch.h"
229230
#include "clang/Lex/Preprocessor.h"
230231
#include "clang/Lex/ModuleMap.h"
@@ -3853,6 +3854,105 @@ static bool FileExists(const char *file)
38533854
return (stat(file, &buf) == 0);
38543855
}
38553856

3857+
////////////////////////////////////////////////////////////////////////////////
3858+
/// Custom diag client for clang that verifies that each implicitly build module
3859+
/// is a system module. If not, it will let the current rootcling invocation
3860+
/// fail with an error. All other diags beside module build remarks will be
3861+
/// forwarded to the passed child diag client.
3862+
///
3863+
/// The reason why we need this is that if we built implicitly a C++ module
3864+
/// that belongs to a ROOT dictionary, then we will miss information generated
3865+
/// by rootcling in this file (e.g. the source code comments to annotation
3866+
/// attributes transformation will be missing in the module file).
3867+
class CheckModuleBuildClient : public clang::DiagnosticConsumer {
3868+
clang::DiagnosticConsumer *fChild;
3869+
bool fOwnsChild;
3870+
clang::ModuleMap &fMap;
3871+
3872+
public:
3873+
CheckModuleBuildClient(clang::DiagnosticConsumer *Child, bool OwnsChild, clang::ModuleMap &Map)
3874+
: fChild(Child), fOwnsChild(OwnsChild), fMap(Map)
3875+
{
3876+
}
3877+
3878+
~CheckModuleBuildClient()
3879+
{
3880+
if (fOwnsChild)
3881+
delete fChild;
3882+
}
3883+
3884+
virtual void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic &Info) override
3885+
{
3886+
using namespace clang::diag;
3887+
3888+
// This method catches the module_build remark from clang and checks if
3889+
// the implicitly built module is a system module or not. We only support
3890+
// building system modules implicitly.
3891+
3892+
std::string moduleName;
3893+
const clang::Module *module = nullptr;
3894+
3895+
// Extract the module from the diag argument with index 0.
3896+
const auto &ID = Info.getID();
3897+
if (ID == remark_module_build || ID == remark_module_build_done) {
3898+
moduleName = Info.getArgStdStr(0);
3899+
module = fMap.findModule(moduleName);
3900+
// We should never be able to build a module without having it in the
3901+
// modulemap. Still, let's print a warning that we at least tell the
3902+
// user that this could lead to problems.
3903+
if (!module) {
3904+
ROOT::TMetaUtils::Warning(0,
3905+
"Couldn't find module %s in the available modulemaps. This"
3906+
"prevents us from correctly diagnosing wrongly built modules.\n",
3907+
moduleName.c_str());
3908+
}
3909+
}
3910+
3911+
// Skip the diag only if we build a system module. We still print the diag
3912+
// when building a non-system module as we will print an error below and the
3913+
// user should see the detailed default clang diagnostic.
3914+
bool isSystemModuleDiag = module && module->IsSystem;
3915+
if (!isSystemModuleDiag)
3916+
fChild->HandleDiagnostic(DiagLevel, Info);
3917+
3918+
if (ID == remark_module_build && !isSystemModuleDiag) {
3919+
ROOT::TMetaUtils::Error(0,
3920+
"Had to build non-system module %s implicitly. You first need to\n"
3921+
"generate the dictionary for %s or mark the C++ module as a system\n"
3922+
"module if you provided your own system modulemap file:\n"
3923+
"%s [system] { ... }\n",
3924+
moduleName.c_str(), moduleName.c_str(), moduleName.c_str());
3925+
}
3926+
}
3927+
3928+
// All methods below just forward to the child and the default method.
3929+
virtual void clear() override
3930+
{
3931+
fChild->clear();
3932+
DiagnosticConsumer::clear();
3933+
}
3934+
3935+
virtual void BeginSourceFile(const clang::LangOptions &LangOpts, const clang::Preprocessor *PP) override
3936+
{
3937+
fChild->BeginSourceFile(LangOpts, PP);
3938+
DiagnosticConsumer::BeginSourceFile(LangOpts, PP);
3939+
}
3940+
3941+
virtual void EndSourceFile() override
3942+
{
3943+
fChild->EndSourceFile();
3944+
DiagnosticConsumer::EndSourceFile();
3945+
}
3946+
3947+
virtual void finish() override
3948+
{
3949+
fChild->finish();
3950+
DiagnosticConsumer::finish();
3951+
}
3952+
3953+
virtual bool IncludeInDiagnosticCounts() const override { return fChild->IncludeInDiagnosticCounts(); }
3954+
};
3955+
38563956
////////////////////////////////////////////////////////////////////////////////
38573957

38583958
int RootClingMain(int argc,
@@ -4292,10 +4392,6 @@ int RootClingMain(int argc,
42924392
// the shared library.
42934393
clingArgsInterpreter.push_back("-fmodules-cache-path=" +
42944394
llvm::sys::path::parent_path(sharedLibraryPathName).str());
4295-
4296-
// We enable remarks in clang for building on-demand modules. This is
4297-
// useful to figure out when and why on-demand modules are built by clang.
4298-
clingArgsInterpreter.push_back("-Rmodule-build");
42994395
}
43004396

43014397
// Convert arguments to a C array and check if they are sane
@@ -4342,9 +4438,27 @@ int RootClingMain(int argc,
43424438
interpPtr = owningInterpPtr.get();
43434439
}
43444440
cling::Interpreter &interp = *interpPtr;
4441+
clang::CompilerInstance *CI = interp.getCI();
43454442
// FIXME: Remove this once we switch cling to use the driver. This would handle -fmodules-embed-all-files for us.
4346-
interp.getCI()->getFrontendOpts().ModulesEmbedAllFiles = true;
4347-
interp.getCI()->getSourceManager().setAllFilesAreTransient(true);
4443+
CI->getFrontendOpts().ModulesEmbedAllFiles = true;
4444+
CI->getSourceManager().setAllFilesAreTransient(true);
4445+
4446+
clang::Preprocessor &PP = CI->getPreprocessor();
4447+
clang::HeaderSearch &headerSearch = PP.getHeaderSearchInfo();
4448+
clang::ModuleMap &moduleMap = headerSearch.getModuleMap();
4449+
auto &diags = interp.getDiagnostics();
4450+
4451+
// Manually enable the module build remarks. We don't enable them via the
4452+
// normal clang command line arg because otherwise we would get remarks for
4453+
// building STL/libc when starting the interpreter in rootcling_stage1.
4454+
// We can't prevent these diags in any other way because we can only attach
4455+
// our own diag client now after the interpreter has already started.
4456+
diags.setSeverity(clang::diag::remark_module_build, clang::diag::Severity::Remark, clang::SourceLocation());
4457+
4458+
// Attach our own diag client that listens to the module_build remarks from
4459+
// clang to check that we don't build dictionary C++ modules implicitly.
4460+
auto recordingClient = new CheckModuleBuildClient(diags.getClient(), diags.ownsClient(), moduleMap);
4461+
diags.setClient(recordingClient, true);
43484462

43494463
if (ROOT::TMetaUtils::GetErrorIgnoreLevel() == ROOT::TMetaUtils::kInfo) {
43504464
ROOT::TMetaUtils::Info(0, "\n");
@@ -4537,7 +4651,6 @@ int RootClingMain(int argc,
45374651

45384652
// Ignore these #pragmas to suppress "unknown pragma" warnings.
45394653
// See LinkdefReader.cxx.
4540-
clang::Preprocessor& PP = interp.getCI()->getPreprocessor();
45414654
PP.AddPragmaHandler(new IgnoringPragmaHandler("link"));
45424655
PP.AddPragmaHandler(new IgnoringPragmaHandler("extra_include"));
45434656
PP.AddPragmaHandler(new IgnoringPragmaHandler("read"));
@@ -4650,7 +4763,6 @@ int RootClingMain(int argc,
46504763
ROOT::TMetaUtils::RConstructorTypes constructorTypes;
46514764

46524765
// Select using DictSelection
4653-
clang::CompilerInstance *CI = interp.getCI();
46544766
const unsigned int selRulesInitialSize = selectionRules.Size();
46554767
if (dictSelection && !onepcm)
46564768
DictSelectionReader dictSelReader(interp, selectionRules, CI->getASTContext(), normCtxt);

0 commit comments

Comments
 (0)