[C++20] [Modules] Judge current module correctly

Now the implementation would accept following code:
```
//--- impl.cppm
module M:impl;
class A {};

//--- M.cppm
export module M;
import :impl;

//--- Use.cpp
import M;
void test() {
    A a; // Expected error. A is not visible here.
}
```

which is clearly wrong.  The root cause is the implementation of
`isInCurrentModule` would return true if the module is a partition! So
in the above example, although Use.cpp is not a module unit,
`isInCurrentModule ` would still return true when the compiler tries to
see if the owning module of `A` is the current module. I believe this is
an oversight. This patch tries to fix this problem.

Reviewed By: iains

Differential Revision: https://reviews.llvm.org/D123837
This commit is contained in:
Chuanqi Xu 2022-04-21 11:08:52 +08:00
parent 483efc9ad0
commit ce2257d69f
5 changed files with 138 additions and 16 deletions

View File

@ -168,6 +168,8 @@ public:
/// some C++ module.
bool isGlobalModule() const { return Kind == GlobalModuleFragment; }
bool isPrivateModule() const { return Kind == PrivateModuleFragment; }
private:
/// The submodules of this module, indexed by name.
std::vector<Module *> SubModules;
@ -536,10 +538,23 @@ public:
/// Get the primary module interface name from a partition.
StringRef getPrimaryModuleInterfaceName() const {
// Technically, global module fragment belongs to global module. And global
// module has no name: [module.unit]p6:
// The global module has no name, no module interface unit, and is not
// introduced by any module-declaration.
//
// <global> is the default name showed in module map.
if (isGlobalModule())
return "<global>";
if (isModulePartition()) {
auto pos = Name.find(':');
return StringRef(Name.data(), pos);
}
if (isPrivateModule())
return getTopLevelModuleName();
return Name;
}

View File

@ -2239,7 +2239,7 @@ private:
/// Namespace definitions that we will export when they finish.
llvm::SmallPtrSet<const NamespaceDecl*, 8> DeferredExportedNamespaces;
/// Get the module whose scope we are currently within.
/// Get the module unit whose scope we are currently within.
Module *getCurrentModule() const {
return ModuleScopes.empty() ? nullptr : ModuleScopes.back().Module;
}
@ -2257,6 +2257,11 @@ private:
VisibleModuleSet VisibleModules;
/// Cache for module units which is usable for current module.
llvm::DenseSet<const Module *> UsableModuleUnitsCache;
bool isUsableModule(const Module *M);
public:
/// Get the module owning an entity.
Module *getOwningModule(const Decl *Entity) {

View File

@ -1558,17 +1558,39 @@ llvm::DenseSet<Module*> &Sema::getLookupModules() {
return LookupModulesCache;
}
/// Determine whether the module M is part of the current module from the
/// perspective of a module-private visibility check.
static bool isInCurrentModule(const Module *M, const LangOptions &LangOpts) {
/// Determine if we could use all the declarations in the module.
bool Sema::isUsableModule(const Module *M) {
assert(M && "We shouldn't check nullness for module here");
// Return quickly if we cached the result.
if (UsableModuleUnitsCache.count(M))
return true;
// If M is the global module fragment of a module that we've not yet finished
// parsing, then it must be part of the current module.
// If it's a partition, then it must be visible to an importer (since only
// another partition or the named module can import it).
return M->getTopLevelModuleName() == LangOpts.CurrentModule ||
(M->Kind == Module::GlobalModuleFragment && !M->Parent) ||
M->Kind == Module::ModulePartitionInterface ||
M->Kind == Module::ModulePartitionImplementation;
// parsing, then M should be the global module fragment in current TU. So it
// should be usable.
// [module.global.frag]p1:
// The global module fragment can be used to provide declarations that are
// attached to the global module and usable within the module unit.
if ((M->isGlobalModule() && !M->Parent) ||
// If M is the private module fragment, it is usable only if it is within
// the current module unit. And it must be the current parsing module unit
// if it is within the current module unit according to the grammar of the
// private module fragment.
// NOTE: This is covered by the following condition. The intention of the
// check is to avoid string comparison as much as possible.
(M->isPrivateModule() && M == getCurrentModule()) ||
// The module unit which is in the same module with the current module
// unit is usable.
//
// FIXME: Here we judge if they are in the same module by comparing the
// string. Is there any better solution?
(M->getPrimaryModuleInterfaceName() ==
llvm::StringRef(getLangOpts().CurrentModule).split(':').first)) {
UsableModuleUnitsCache.insert(M);
return true;
}
return false;
}
bool Sema::hasVisibleMergedDefinition(NamedDecl *Def) {
@ -1580,7 +1602,7 @@ bool Sema::hasVisibleMergedDefinition(NamedDecl *Def) {
bool Sema::hasMergedDefinitionInCurrentModule(NamedDecl *Def) {
for (const Module *Merged : Context.getModulesWithMergedDefinition(Def))
if (isInCurrentModule(Merged, getLangOpts()))
if (isUsableModule(Merged))
return true;
return false;
}
@ -1762,7 +1784,7 @@ bool Sema::isModuleVisible(const Module *M, bool ModulePrivate) {
// means it is part of the current module. For any other query, that means it
// is in our visible module set.
if (ModulePrivate) {
if (isInCurrentModule(M, getLangOpts()))
if (isUsableModule(M))
return true;
else if (M->Kind == Module::ModuleKind::ModulePartitionImplementation &&
isModuleDirectlyImported(M))

View File

@ -0,0 +1,73 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
// RUN: %clang_cc1 -std=c++20 %t/impl.cppm -emit-module-interface -o %t/M-impl.pcm
// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -fprebuilt-module-path=%t -o %t/M.pcm
// RUN: %clang_cc1 -std=c++20 %t/Use.cpp -fprebuilt-module-path=%t -verify -fsyntax-only
// RUN: %clang_cc1 -std=c++20 %t/UseInPartA.cppm -fprebuilt-module-path=%t -verify -fsyntax-only
// RUN: %clang_cc1 -std=c++20 %t/UseInAnotherModule.cppm -fprebuilt-module-path=%t -verify -fsyntax-only
// RUN: %clang_cc1 -std=c++20 %t/Private.cppm -emit-module-interface -fprebuilt-module-path=%t -o %t/A.pcm
// RUN: %clang_cc1 -std=c++20 %t/TryUseFromPrivate.cpp -fprebuilt-module-path=%t -verify -fsyntax-only
// RUN: %clang_cc1 -std=c++20 %t/Global.cppm -emit-module-interface -fprebuilt-module-path=%t -o %t/C.pcm
// RUN: %clang_cc1 -std=c++20 %t/UseGlobal.cpp -fprebuilt-module-path=%t -verify -fsyntax-only
//--- impl.cppm
module M:impl;
class A {};
//--- M.cppm
export module M;
import :impl;
export A f();
//--- Use.cpp
import M;
void test() {
A a; // expected-error {{unknown type name 'A'}}
}
//--- UseInPartA.cppm
// expected-no-diagnostics
export module M:partA;
import :impl;
void test() {
A a;
}
//--- UseInAnotherModule.cppm
export module B;
import M;
void test() {
A a; // expected-error {{unknown type name 'A'}}
}
//--- Private.cppm
export module A;
module :private;
class A {};
void test() {
A a;
}
//--- TryUseFromPrivate.cpp
import A;
void test() {
A a; // expected-error {{unknown type name 'A'}}
}
//--- Global.cppm
module;
class A{};
export module C;
void test() {
A a;
}
//--- UseGlobal.cpp
import C;
void test() {
A a; // expected-error {{'A' must be declared before it is used}}
// expected-note@Global.cppm:2 {{declaration here is not visible}}
}

View File

@ -56,7 +56,14 @@ import B;
int &c = n; // expected-error {{use of undeclared identifier}}
//--- std10-1-ex2-tu7.cpp
// expected-no-diagnostics
module B:X3; // does not implicitly import B
import :X2; // X2 is an implementation so exports nothing.
// error: n not visible here.
int &c = n; // expected-error {{use of undeclared identifier }}
import :X2; // X2 is an implementation unit import B.
// According to [module.import]p7:
// Additionally, when a module-import-declaration in a module unit of some
// module M imports another module unit U of M, it also imports all
// translation units imported by non-exported module-import-declarations in
// the module unit purview of U.
//
// So B is imported in B:X3 due to B:X2 imported B. So n is visible here.
int &c = n;