Skip to content

Commit

Permalink
[C++20] [Modules] Warn for duplicated decls in mutliple module units (#…
Browse files Browse the repository at this point in the history
…105799)

It is a long standing issue that the duplicated declarations in multiple
module units would cause the compilation performance to get slowed down.
And there are many questions or issue reports. So I think it is better
to add a warning for it.

And given this is not because the users' code violates the language
specification or any best practices, the warning is disabled by default
even if `-Wall` is specified. The users need to specify the warning
explcitly or use `Weverything`.

The documentation will add separately.
  • Loading branch information
ChuanqiXu9 authored Aug 23, 2024
1 parent cf6cd1f commit 3cca522
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 3 deletions.
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticSerializationKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ def warn_module_system_bit_conflict : Warning<
"as a non-system module; any difference in diagnostic options will be ignored">,
InGroup<ModuleConflict>;

def warn_decls_in_multiple_modules : Warning<
"declaration %0 is detected to be defined in multiple module units, first is from '%1' and second is from '%2'; "
"the compiler may not be good at merging the definitions. ">,
InGroup<DiagGroup<"decls-in-multiple-modules">>,
DefaultIgnore;

def err_failed_to_find_module_file : Error<
"failed to find module file for module '%0'">;
} // let CategoryName
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,12 @@ class ASTReader
/// performed deduplication.
llvm::SetVector<NamedDecl *> PendingMergedDefinitionsToDeduplicate;

/// The duplicated definitions in module units which are pending to be warned.
/// We need to delay it to wait for the loading of definitions since we don't
/// want to warn for forward declarations.
llvm::SmallVector<std::pair<Decl *, Decl *>>
PendingWarningForDuplicatedDefsInModuleUnits;

/// Read the record that describes the lexical contents of a DC.
bool ReadLexicalDeclContextStorage(ModuleFile &M,
llvm::BitstreamCursor &Cursor,
Expand Down
39 changes: 39 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9955,6 +9955,45 @@ void ASTReader::finishPendingActions() {
}
PendingDefinitions.clear();

for (auto [D, Previous] : PendingWarningForDuplicatedDefsInModuleUnits) {
auto hasDefinitionImpl = [this](Decl *D, auto hasDefinitionImpl) {
if (auto *VD = dyn_cast<VarDecl>(D))
return VD->isThisDeclarationADefinition() ||
VD->isThisDeclarationADemotedDefinition();

if (auto *TD = dyn_cast<TagDecl>(D))
return TD->isThisDeclarationADefinition() ||
TD->isThisDeclarationADemotedDefinition();

if (auto *FD = dyn_cast<FunctionDecl>(D))
return FD->isThisDeclarationADefinition() || PendingBodies.count(FD);

if (auto *RTD = dyn_cast<RedeclarableTemplateDecl>(D))
return hasDefinitionImpl(RTD->getTemplatedDecl(), hasDefinitionImpl);

// Conservatively return false here.
return false;
};

auto hasDefinition = [this, &hasDefinitionImpl](Decl *D) {
return hasDefinitionImpl(D, hasDefinitionImpl);
};

// It is not good to prevent multiple declarations since the forward
// declaration is common. Let's try to avoid duplicated definitions
// only.
if (!hasDefinition(D) || !hasDefinition(Previous))
continue;

Module *PM = Previous->getOwningModule();
Module *DM = D->getOwningModule();
Diag(D->getLocation(), diag::warn_decls_in_multiple_modules)
<< cast<NamedDecl>(Previous) << PM->getTopLevelModuleName()
<< (DM ? DM->getTopLevelModuleName() : "global module");
Diag(Previous->getLocation(), diag::note_also_found);
}
PendingWarningForDuplicatedDefsInModuleUnits.clear();

// Load the bodies of any functions or methods we've encountered. We do
// this now (delayed) so that we can be sure that the declaration chains
// have been fully wired up (hasBody relies on this).
Expand Down
21 changes: 18 additions & 3 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ class ASTDeclReader : public DeclVisitor<ASTDeclReader, void> {
static void attachPreviousDecl(ASTReader &Reader, Decl *D, Decl *Previous,
Decl *Canon);

static void checkMultipleDefinitionInNamedModules(ASTReader &Reader, Decl *D,
Decl *Previous);

template <typename DeclT>
static void attachLatestDeclImpl(Redeclarable<DeclT> *D, Decl *Latest);
static void attachLatestDeclImpl(...);
Expand Down Expand Up @@ -3690,8 +3693,9 @@ static void inheritDefaultTemplateArguments(ASTContext &Context,
// [basic.link]/p10:
// If two declarations of an entity are attached to different modules,
// the program is ill-formed;
static void checkMultipleDefinitionInNamedModules(ASTReader &Reader, Decl *D,
Decl *Previous) {
void ASTDeclReader::checkMultipleDefinitionInNamedModules(ASTReader &Reader,
Decl *D,
Decl *Previous) {
// If it is previous implcitly introduced, it is not meaningful to
// diagnose it.
if (Previous->isImplicit())
Expand Down Expand Up @@ -3721,8 +3725,19 @@ static void checkMultipleDefinitionInNamedModules(ASTReader &Reader, Decl *D,
return;

// We only forbids merging decls within named modules.
if (!M->isNamedModule())
if (!M->isNamedModule()) {
// Try to warn the case that we merged decls from global module.
if (!M->isGlobalModule())
return;

if (D->getOwningModule() &&
M->getTopLevelModule() == D->getOwningModule()->getTopLevelModule())
return;

Reader.PendingWarningForDuplicatedDefsInModuleUnits.push_back(
{D, Previous});
return;
}

// It is fine if they are in the same module.
if (Reader.getContext().isInSameModule(M, D->getOwningModule()))
Expand Down
83 changes: 83 additions & 0 deletions clang/test/Modules/warn-duplicated-decls-in-module-units.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// RUN: rm -rf %t
// RUN: mkdir %t
// RUN: split-file %s %t
//
// RUN: %clang_cc1 -std=c++20 %t/m1.cppm -emit-module-interface -o %t/m1.pcm
// RUN: %clang_cc1 -std=c++20 %t/m2.cppm -emit-module-interface -o %t/m2.pcm
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cc -fsyntax-only \
// RUN: -verify
//
// RUN: %clang_cc1 -std=c++20 %t/m1.cppm -Wall -emit-module-interface -o %t/m1.pcm
// RUN: %clang_cc1 -std=c++20 %t/m2.cppm -Wall -emit-module-interface -o %t/m2.pcm
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cc -fsyntax-only \
// RUN: -verify -Wall
//
// RUN: %clang_cc1 -std=c++20 %t/m1.cppm -Wdecls-in-multiple-modules -emit-module-interface -o %t/m1.pcm
// RUN: %clang_cc1 -std=c++20 %t/m2.cppm -Wdecls-in-multiple-modules -emit-module-interface -o %t/m2.pcm
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cc -fsyntax-only \
// RUN: -verify -Wdecls-in-multiple-modules -DWARNING

//--- foo.h
#ifndef FOO_H
#define FOO_H

enum E { E1, E2 };

int a = 43;

class foo {
public:
void consume(E, int);
};

inline void func() {}

void fwd_decl();

#endif

//--- m1.cppm
module;
#include "foo.h"
export module m1;
export {
using ::foo;
using ::a;
using ::func;
using ::fwd_decl;
using ::E;
}

//--- m2.cppm
module;
#include "foo.h"
export module m2;
export {
using ::foo;
using ::a;
using ::func;
using ::fwd_decl;
using ::E;
}

//--- use.cc
import m1;
import m2;
void use();
void use() {
E e = E1;
foo f;
f.consume(e, a);
func();
fwd_decl();
}

#ifndef WARNING
// expected-no-diagnostics
#else
// expected-warning@* {{declaration 'E' is detected to be defined in multiple module units}}
// expected-warning@* {{declaration 'foo' is detected to be defined in multiple module units}}
// expected-warning@* {{declaration 'a' is detected to be defined in multiple module units}}
// expected-warning@* {{declaration 'func' is detected to be defined in multiple module units}}
// expected-note@* 1+ {{}}
#endif

0 comments on commit 3cca522

Please sign in to comment.